From 48df376f845d06990d0887cb36a6cff14a806662 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 03:25:27 +0000 Subject: [PATCH 001/320] initial commit --- .devcontainer/Dockerfile | 9 + .devcontainer/devcontainer.json | 40 + .github/workflows/ci.yml | 53 + .gitignore | 16 + .python-version | 1 + .stats.yml | 2 + Brewfile | 2 + CONTRIBUTING.md | 129 ++ LICENSE | 201 ++ README.md | 357 ++- SECURITY.md | 27 + api.md | 125 + bin/publish-pypi | 9 + examples/.keep | 4 + mypy.ini | 50 + noxfile.py | 9 + pyproject.toml | 207 ++ requirements-dev.lock | 103 + requirements.lock | 44 + scripts/bootstrap | 19 + scripts/format | 8 + scripts/lint | 12 + scripts/mock | 41 + scripts/test | 59 + scripts/utils/ruffen-docs.py | 167 ++ src/codex/__init__.py | 84 + src/codex/_base_client.py | 2051 +++++++++++++++++ src/codex/_client.py | 518 +++++ src/codex/_compat.py | 219 ++ src/codex/_constants.py | 14 + src/codex/_exceptions.py | 108 + src/codex/_files.py | 123 + src/codex/_models.py | 795 +++++++ src/codex/_qs.py | 150 ++ src/codex/_resource.py | 43 + src/codex/_response.py | 824 +++++++ src/codex/_streaming.py | 333 +++ src/codex/_types.py | 217 ++ src/codex/_utils/__init__.py | 57 + src/codex/_utils/_logs.py | 25 + src/codex/_utils/_proxy.py | 62 + src/codex/_utils/_reflection.py | 42 + src/codex/_utils/_streams.py | 12 + src/codex/_utils/_sync.py | 71 + src/codex/_utils/_transform.py | 392 ++++ src/codex/_utils/_typing.py | 149 ++ src/codex/_utils/_utils.py | 414 ++++ src/codex/_version.py | 4 + src/codex/lib/.keep | 4 + src/codex/py.typed | 0 src/codex/resources/__init__.py | 61 + src/codex/resources/health.py | 235 ++ src/codex/resources/organizations/__init__.py | 33 + src/codex/resources/organizations/billing.py | 242 ++ .../resources/organizations/organizations.py | 195 ++ src/codex/resources/projects/__init__.py | 47 + src/codex/resources/projects/access_keys.py | 594 +++++ src/codex/resources/projects/knowledge.py | 740 ++++++ src/codex/resources/projects/projects.py | 659 ++++++ src/codex/resources/users/__init__.py | 33 + src/codex/resources/users/myself/__init__.py | 47 + src/codex/resources/users/myself/api_key.py | 135 ++ src/codex/resources/users/myself/myself.py | 199 ++ .../resources/users/myself/organizations.py | 135 ++ src/codex/resources/users/users.py | 102 + src/codex/types/__init__.py | 11 + src/codex/types/health_check_response.py | 10 + src/codex/types/organization_schema_public.py | 17 + src/codex/types/organizations/__init__.py | 6 + .../organization_billing_invoices_schema.py | 10 + .../organization_billing_usage_schema.py | 10 + src/codex/types/project_create_params.py | 22 + src/codex/types/project_list_params.py | 11 + src/codex/types/project_list_response.py | 10 + src/codex/types/project_return_schema.py | 30 + src/codex/types/project_update_params.py | 20 + src/codex/types/projects/__init__.py | 15 + .../projects/access_key_create_params.py | 19 + .../projects/access_key_list_response.py | 10 + src/codex/types/projects/access_key_schema.py | 31 + .../projects/access_key_update_params.py | 21 + src/codex/types/projects/entry.py | 20 + .../projects/knowledge_add_question_params.py | 11 + .../types/projects/knowledge_create_params.py | 14 + .../types/projects/knowledge_list_params.py | 21 + .../types/projects/knowledge_query_params.py | 11 + .../types/projects/knowledge_update_params.py | 16 + .../types/projects/list_knowledge_response.py | 14 + src/codex/types/users/__init__.py | 6 + src/codex/types/users/myself/__init__.py | 5 + .../users/myself/user_organizations_schema.py | 22 + src/codex/types/users/user_schema.py | 24 + src/codex/types/users/user_schema_public.py | 17 + tests/__init__.py | 1 + tests/api_resources/__init__.py | 1 + tests/api_resources/organizations/__init__.py | 1 + .../organizations/test_billing.py | 174 ++ tests/api_resources/projects/__init__.py | 1 + .../projects/test_access_keys.py | 476 ++++ .../api_resources/projects/test_knowledge.py | 607 +++++ tests/api_resources/test_health.py | 172 ++ tests/api_resources/test_organizations.py | 98 + tests/api_resources/test_projects.py | 461 ++++ tests/api_resources/users/__init__.py | 1 + tests/api_resources/users/myself/__init__.py | 1 + .../users/myself/test_api_key.py | 72 + .../users/myself/test_organizations.py | 72 + tests/api_resources/users/test_myself.py | 72 + tests/conftest.py | 65 + tests/sample_file.txt | 1 + tests/test_client.py | 1873 +++++++++++++++ tests/test_deepcopy.py | 58 + tests/test_extract_files.py | 64 + tests/test_files.py | 51 + tests/test_models.py | 856 +++++++ tests/test_qs.py | 78 + tests/test_required_args.py | 111 + tests/test_response.py | 277 +++ tests/test_streaming.py | 248 ++ tests/test_transform.py | 425 ++++ tests/test_utils/test_proxy.py | 23 + tests/test_utils/test_typing.py | 73 + tests/utils.py | 159 ++ 123 files changed, 18867 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 .stats.yml create mode 100644 Brewfile create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md create mode 100644 api.md create mode 100644 bin/publish-pypi create mode 100644 examples/.keep create mode 100644 mypy.ini create mode 100644 noxfile.py create mode 100644 pyproject.toml create mode 100644 requirements-dev.lock create mode 100644 requirements.lock create mode 100755 scripts/bootstrap create mode 100755 scripts/format create mode 100755 scripts/lint create mode 100755 scripts/mock create mode 100755 scripts/test create mode 100644 scripts/utils/ruffen-docs.py create mode 100644 src/codex/__init__.py create mode 100644 src/codex/_base_client.py create mode 100644 src/codex/_client.py create mode 100644 src/codex/_compat.py create mode 100644 src/codex/_constants.py create mode 100644 src/codex/_exceptions.py create mode 100644 src/codex/_files.py create mode 100644 src/codex/_models.py create mode 100644 src/codex/_qs.py create mode 100644 src/codex/_resource.py create mode 100644 src/codex/_response.py create mode 100644 src/codex/_streaming.py create mode 100644 src/codex/_types.py create mode 100644 src/codex/_utils/__init__.py create mode 100644 src/codex/_utils/_logs.py create mode 100644 src/codex/_utils/_proxy.py create mode 100644 src/codex/_utils/_reflection.py create mode 100644 src/codex/_utils/_streams.py create mode 100644 src/codex/_utils/_sync.py create mode 100644 src/codex/_utils/_transform.py create mode 100644 src/codex/_utils/_typing.py create mode 100644 src/codex/_utils/_utils.py create mode 100644 src/codex/_version.py create mode 100644 src/codex/lib/.keep create mode 100644 src/codex/py.typed create mode 100644 src/codex/resources/__init__.py create mode 100644 src/codex/resources/health.py create mode 100644 src/codex/resources/organizations/__init__.py create mode 100644 src/codex/resources/organizations/billing.py create mode 100644 src/codex/resources/organizations/organizations.py create mode 100644 src/codex/resources/projects/__init__.py create mode 100644 src/codex/resources/projects/access_keys.py create mode 100644 src/codex/resources/projects/knowledge.py create mode 100644 src/codex/resources/projects/projects.py create mode 100644 src/codex/resources/users/__init__.py create mode 100644 src/codex/resources/users/myself/__init__.py create mode 100644 src/codex/resources/users/myself/api_key.py create mode 100644 src/codex/resources/users/myself/myself.py create mode 100644 src/codex/resources/users/myself/organizations.py create mode 100644 src/codex/resources/users/users.py create mode 100644 src/codex/types/__init__.py create mode 100644 src/codex/types/health_check_response.py create mode 100644 src/codex/types/organization_schema_public.py create mode 100644 src/codex/types/organizations/__init__.py create mode 100644 src/codex/types/organizations/organization_billing_invoices_schema.py create mode 100644 src/codex/types/organizations/organization_billing_usage_schema.py create mode 100644 src/codex/types/project_create_params.py create mode 100644 src/codex/types/project_list_params.py create mode 100644 src/codex/types/project_list_response.py create mode 100644 src/codex/types/project_return_schema.py create mode 100644 src/codex/types/project_update_params.py create mode 100644 src/codex/types/projects/__init__.py create mode 100644 src/codex/types/projects/access_key_create_params.py create mode 100644 src/codex/types/projects/access_key_list_response.py create mode 100644 src/codex/types/projects/access_key_schema.py create mode 100644 src/codex/types/projects/access_key_update_params.py create mode 100644 src/codex/types/projects/entry.py create mode 100644 src/codex/types/projects/knowledge_add_question_params.py create mode 100644 src/codex/types/projects/knowledge_create_params.py create mode 100644 src/codex/types/projects/knowledge_list_params.py create mode 100644 src/codex/types/projects/knowledge_query_params.py create mode 100644 src/codex/types/projects/knowledge_update_params.py create mode 100644 src/codex/types/projects/list_knowledge_response.py create mode 100644 src/codex/types/users/__init__.py create mode 100644 src/codex/types/users/myself/__init__.py create mode 100644 src/codex/types/users/myself/user_organizations_schema.py create mode 100644 src/codex/types/users/user_schema.py create mode 100644 src/codex/types/users/user_schema_public.py create mode 100644 tests/__init__.py create mode 100644 tests/api_resources/__init__.py create mode 100644 tests/api_resources/organizations/__init__.py create mode 100644 tests/api_resources/organizations/test_billing.py create mode 100644 tests/api_resources/projects/__init__.py create mode 100644 tests/api_resources/projects/test_access_keys.py create mode 100644 tests/api_resources/projects/test_knowledge.py create mode 100644 tests/api_resources/test_health.py create mode 100644 tests/api_resources/test_organizations.py create mode 100644 tests/api_resources/test_projects.py create mode 100644 tests/api_resources/users/__init__.py create mode 100644 tests/api_resources/users/myself/__init__.py create mode 100644 tests/api_resources/users/myself/test_api_key.py create mode 100644 tests/api_resources/users/myself/test_organizations.py create mode 100644 tests/api_resources/users/test_myself.py create mode 100644 tests/conftest.py create mode 100644 tests/sample_file.txt create mode 100644 tests/test_client.py create mode 100644 tests/test_deepcopy.py create mode 100644 tests/test_extract_files.py create mode 100644 tests/test_files.py create mode 100644 tests/test_models.py create mode 100644 tests/test_qs.py create mode 100644 tests/test_required_args.py create mode 100644 tests/test_response.py create mode 100644 tests/test_streaming.py create mode 100644 tests/test_transform.py create mode 100644 tests/test_utils/test_proxy.py create mode 100644 tests/test_utils/test_typing.py create mode 100644 tests/utils.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..ac9a2e75 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,9 @@ +ARG VARIANT="3.9" +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 +ENV PATH=/home/vscode/.rye/shims:$PATH + +RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..bbeb30b1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,40 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "Debian", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + + "postStartCommand": "rye sync --all-features", + + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": ".venv/bin/python", + "python.defaultInterpreterPath": ".venv/bin/python", + "python.typeChecking": "basic", + "terminal.integrated.env.linux": { + "PATH": "/home/vscode/.rye/shims:${env:PATH}" + } + } + } + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..40293964 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: CI +on: + push: + branches: + - main + pull_request: + branches: + - main + - next + +jobs: + lint: + name: lint + runs-on: ubuntu-latest + + + steps: + - uses: actions/checkout@v4 + + - 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_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run lints + run: ./scripts/lint + test: + name: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - 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_INSTALL_OPTION: '--yes' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Run tests + run: ./scripts/test + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..87797408 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.prism.log +.vscode +_dev + +__pycache__ +.mypy_cache + +dist + +.venv +.idea + +.env +.envrc +codegen.log +Brewfile.lock.json diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..43077b24 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.9.18 diff --git a/.stats.yml b/.stats.yml new file mode 100644 index 00000000..71d28223 --- /dev/null +++ b/.stats.yml @@ -0,0 +1,2 @@ +configured_endpoints: 28 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-f4a89fd69be5055c12ea6d3aa795af4161605848eb9e010e1a7be22f4b1b712c.yml diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000..492ca37b --- /dev/null +++ b/Brewfile @@ -0,0 +1,2 @@ +brew "rye" + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..7111bafc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,129 @@ +## Setting up the environment + +### With Rye + +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: + +```sh +$ ./scripts/bootstrap +``` + +Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run: + +```sh +$ 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 +$ source .venv/bin/activate + +# now you can omit the `rye run` prefix +$ python script.py +``` + +### Without Rye + +Alternatively if you don't want to install `Rye`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command: + +```sh +$ pip install -r requirements-dev.lock +``` + +## Modifying/Adding code + +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/codex/lib/` and `examples/` directories. + +## Adding and running examples + +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. + +```py +# add an example to examples/.py + +#!/usr/bin/env -S rye run python +… +``` + +```sh +$ chmod +x examples/.py +# run the example against your api +$ ./examples/.py +``` + +## Using the repository from source + +If you’d like to use the repository from source, you can either install from git or link to a cloned repository: + +To install via git: + +```sh +$ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git +``` + +Alternatively, you can build from source and install the wheel file: + +Building this package will create two files in the `dist/` directory, a `.tar.gz` containing the source files and a `.whl` that can be used to install the package efficiently. + +To create a distributable version of the library, all you have to do is run this command: + +```sh +$ rye build +# or +$ python -m build +``` + +Then to install: + +```sh +$ pip install ./path-to-wheel-file.whl +``` + +## Running tests + +Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. + +```sh +# you will need npm installed +$ npx prism mock path/to/your/openapi.yml +``` + +```sh +$ ./scripts/test +``` + +## Linting and formatting + +This repository uses [ruff](https://github.com/astral-sh/ruff) and +[black](https://github.com/psf/black) to format the code in the repository. + +To lint: + +```sh +$ ./scripts/lint +``` + +To format and fix all ruff issues automatically: + +```sh +$ ./scripts/format +``` + +## Publishing and releases + +Changes made to this repository via the automated release PR pipeline should publish to PyPI automatically. If +the changes aren't made through the automated pipeline, you may want to make releases manually. + +### Publish with a GitHub workflow + +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/stainless-sdks/codex-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. + +### Publish manually + +If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on +the environment. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f00d3a6a --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Codex + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 40f5fc62..e4bf463c 100644 --- a/README.md +++ b/README.md @@ -1 +1,356 @@ -# codex-python \ No newline at end of file +# Codex Python API library + +[![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/) + +The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+ +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/). + +## Documentation + +The REST API documentation can be found on [docs.codex.com](https://docs.codex.com). The full API of this library can be found in [api.md](api.md). + +## Installation + +```sh +# install from this staging repo +pip install git+ssh://git@github.com/stainless-sdks/codex-python.git +``` + +> [!NOTE] +> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex` + +## Usage + +The full API of this library can be found in [api.md](api.md). + +```python +from codex import Codex + +client = Codex() + +project_return_schema = client.projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) +print(project_return_schema.id) +``` + +While you can provide a `bearer_token` keyword argument, +we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) +to add `BEARER_TOKEN="My Bearer Token"` to your `.env` file +so that your Bearer Token is not stored in source control. + +## Async usage + +Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call: + +```python +import asyncio +from codex import AsyncCodex + +client = AsyncCodex() + + +async def main() -> None: + project_return_schema = await client.projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + print(project_return_schema.id) + + +asyncio.run(main()) +``` + +Functionality between the synchronous and asynchronous clients is otherwise identical. + +## 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: + +- Serializing back into JSON, `model.to_json()` +- Converting to a dictionary, `model.to_dict()` + +Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. + +## Handling errors + +When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised. + +When the API returns a non-success status code (that is, 4xx or 5xx +response), a subclass of `codex.APIStatusError` is raised, containing `status_code` and `response` properties. + +All errors inherit from `codex.APIError`. + +```python +import codex +from codex import Codex + +client = Codex() + +try: + client.projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) +except codex.APIConnectionError as e: + print("The server could not be reached") + print(e.__cause__) # an underlying Exception, likely raised within httpx. +except codex.RateLimitError as e: + print("A 429 status code was received; we should back off a bit.") +except codex.APIStatusError as e: + print("Another non-200-range status code was received") + print(e.status_code) + print(e.response) +``` + +Error codes are as follows: + +| Status Code | Error Type | +| ----------- | -------------------------- | +| 400 | `BadRequestError` | +| 401 | `AuthenticationError` | +| 403 | `PermissionDeniedError` | +| 404 | `NotFoundError` | +| 422 | `UnprocessableEntityError` | +| 429 | `RateLimitError` | +| >=500 | `InternalServerError` | +| N/A | `APIConnectionError` | + +### Retries + +Certain errors are automatically retried 2 times by default, with a short exponential backoff. +Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, +429 Rate Limit, and >=500 Internal errors are all retried by default. + +You can use the `max_retries` option to configure or disable retry settings: + +```python +from codex import Codex + +# Configure the default for all requests: +client = Codex( + # default is 2 + max_retries=0, +) + +# Or, configure per-request: +client.with_options(max_retries=5).projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) +``` + +### 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: + +```python +from codex import Codex + +# Configure the default for all requests: +client = Codex( + # 20 seconds (default is 1 minute) + timeout=20.0, +) + +# More granular control: +client = Codex( + timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), +) + +# Override per-request: +client.with_options(timeout=5.0).projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) +``` + +On timeout, an `APITimeoutError` is thrown. + +Note that requests that time out are [retried twice by default](#retries). + +## Advanced + +### Logging + +We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. + +You can enable logging by setting the environment variable `CODEX_LOG` to `info`. + +```shell +$ export CODEX_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`: + +```py +if response.my_field is None: + if 'my_field' not in response.model_fields_set: + print('Got json like {}, without a "my_field" key present at all.') + else: + print('Got json like {"my_field": null}.') +``` + +### Accessing raw response data (e.g. headers) + +The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., + +```py +from codex import Codex + +client = Codex() +response = client.projects.with_raw_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) +print(response.headers.get('X-My-Header')) + +project = response.parse() # get the object that `projects.create()` would have returned +print(project.id) +``` + +These methods return an [`APIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) object. + +The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. + +#### `.with_streaming_response` + +The above interface eagerly reads the full response body when you make the request, which may not always be what you want. + +To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. + +```python +with client.projects.with_streaming_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) as response: + print(response.headers.get("X-My-Header")) + + for line in response.iter_lines(): + print(line) +``` + +The context manager is required so that the response will reliably be closed. + +### Making custom/undocumented requests + +This library is typed for convenient access to the documented API. + +If you need to access undocumented endpoints, params, or response properties, the library can still be used. + +#### 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) when making this request. + +```py +import httpx + +response = client.post( + "/foo", + cast_to=httpx.Response, + body={"my_param": True}, +) + +print(response.headers.get("x-foo")) +``` + +#### Undocumented request params + +If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request +options. + +#### Undocumented response properties + +To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You +can also get all the extra fields on the Pydantic model as a dict with +[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra). + +### Configuring the HTTP client + +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](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 codex import Codex, DefaultHttpxClient + +client = Codex( + # Or use the `CODEX_BASE_URL` env var + base_url="http://my.test.server.example.com:8083", + http_client=DefaultHttpxClient( + proxy="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + +You can also customize the client on a per-request basis by using `with_options()`: + +```python +client.with_options(http_client=DefaultHttpxClient(...)) +``` + +### Managing HTTP resources + +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 codex import Codex + +with Codex() 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.)_ +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/stainless-sdks/codex-python/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 codex +print(codex.__version__) +``` + +## Requirements + +Python 3.8 or higher. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..cf89cd01 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## 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. + +To report a security issue, please contact the Stainless team at security@stainlessapi.com. + +## Responsible Disclosure + +We appreciate the efforts of security researchers and individuals who help us maintain the security of +SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible +disclosure practices by allowing us a reasonable amount of time to investigate and address the issue +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 Codex please follow the respective company's security reporting guidelines. + +### Codex Terms and Policies + +Please contact dev-feedback@codex.com for any questions or concerns regarding security of our services. + +--- + +Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/api.md b/api.md new file mode 100644 index 00000000..c9ef6c6c --- /dev/null +++ b/api.md @@ -0,0 +1,125 @@ +# Health + +Types: + +```python +from codex.types import HealthCheckResponse +``` + +Methods: + +- client.health.check() -> HealthCheckResponse +- client.health.db() -> HealthCheckResponse +- client.health.weaviate() -> HealthCheckResponse + +# Organizations + +Types: + +```python +from codex.types import OrganizationSchemaPublic +``` + +Methods: + +- client.organizations.retrieve(organization_id) -> OrganizationSchemaPublic + +## Billing + +Types: + +```python +from codex.types.organizations import ( + OrganizationBillingInvoicesSchema, + OrganizationBillingUsageSchema, +) +``` + +Methods: + +- client.organizations.billing.invoices(organization_id) -> OrganizationBillingInvoicesSchema +- client.organizations.billing.usage(organization_id) -> OrganizationBillingUsageSchema + +# Users + +## Myself + +Types: + +```python +from codex.types.users import UserSchema, UserSchemaPublic +``` + +Methods: + +- client.users.myself.retrieve() -> UserSchemaPublic + +### APIKey + +Methods: + +- client.users.myself.api_key.refresh() -> UserSchema + +### Organizations + +Types: + +```python +from codex.types.users.myself import UserOrganizationsSchema +``` + +Methods: + +- client.users.myself.organizations.list() -> UserOrganizationsSchema + +# Projects + +Types: + +```python +from codex.types import ProjectReturnSchema, ProjectListResponse, ProjectExportResponse +``` + +Methods: + +- client.projects.create(\*\*params) -> ProjectReturnSchema +- client.projects.retrieve(project_id) -> ProjectReturnSchema +- client.projects.update(project_id, \*\*params) -> ProjectReturnSchema +- client.projects.list(\*\*params) -> ProjectListResponse +- client.projects.delete(project_id) -> None +- client.projects.export(project_id) -> object + +## AccessKeys + +Types: + +```python +from codex.types.projects import AccessKeySchema, AccessKeyListResponse +``` + +Methods: + +- client.projects.access_keys.create(project_id, \*\*params) -> AccessKeySchema +- client.projects.access_keys.retrieve(access_key_id, \*, project_id) -> AccessKeySchema +- client.projects.access_keys.update(access_key_id, \*, project_id, \*\*params) -> AccessKeySchema +- client.projects.access_keys.list(project_id) -> AccessKeyListResponse +- client.projects.access_keys.delete(access_key_id, \*, project_id) -> None +- client.projects.access_keys.revoke(access_key_id, \*, project_id) -> None + +## Knowledge + +Types: + +```python +from codex.types.projects import Entry, ListKnowledgeResponse +``` + +Methods: + +- client.projects.knowledge.create(project_id, \*\*params) -> Entry +- client.projects.knowledge.retrieve(entry_id, \*, project_id) -> Entry +- client.projects.knowledge.update(entry_id, \*, project_id, \*\*params) -> Entry +- client.projects.knowledge.list(project_id, \*\*params) -> ListKnowledgeResponse +- client.projects.knowledge.delete(entry_id, \*, project_id) -> None +- client.projects.knowledge.add_question(project_id, \*\*params) -> Entry +- client.projects.knowledge.query(project_id, \*\*params) -> Optional[Entry] diff --git a/bin/publish-pypi b/bin/publish-pypi new file mode 100644 index 00000000..05bfccbb --- /dev/null +++ b/bin/publish-pypi @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +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/.keep b/examples/.keep new file mode 100644 index 00000000..d8c73e93 --- /dev/null +++ b/examples/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store example files demonstrating usage of this SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..88ad64a6 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,50 @@ +[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/codex/_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 + +# https://github.com/python/mypy/issues/12162 +[mypy.overrides] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..53bca7ff --- /dev/null +++ b/noxfile.py @@ -0,0 +1,9 @@ +import nox + + +@nox.session(reuse_venv=True, name="test-pydantic-v1") +def test_pydantic_v1(session: nox.Session) -> None: + session.install("-r", "requirements-dev.lock") + session.install("pydantic<2") + + session.run("pytest", "--showlocals", "--ignore=tests/functional", *session.posargs) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..5116f227 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,207 @@ +[project] +name = "codex" +version = "0.0.1-alpha.0" +description = "The official Python library for the codex API" +dynamic = ["readme"] +license = "Apache-2.0" +authors = [ +{ name = "Codex", email = "dev-feedback@codex.com" }, +] +dependencies = [ + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.10, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", +] +requires-python = ">= 3.8" +classifiers = [ + "Typing :: Typed", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: Apache Software License" +] + +[project.urls] +Homepage = "https://github.com/stainless-sdks/codex-python" +Repository = "https://github.com/stainless-sdks/codex-python" + + + +[tool.rye] +managed = true +# version pins are in requirements-dev.lock +dev-dependencies = [ + "pyright>=1.1.359", + "mypy", + "respx", + "pytest", + "pytest-asyncio", + "ruff", + "time-machine", + "nox", + "dirty-equals>=0.6.0", + "importlib-metadata>=6.7.0", + "rich>=13.7.1", + "nest_asyncio==1.6.0", +] + +[tool.rye.scripts] +format = { chain = [ + "format:ruff", + "format:docs", + "fix:ruff", + # run formatting again to fix any inconsistencies when imports are stripped + "format:ruff", +]} +"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" +"format:ruff" = "ruff format" + +"lint" = { chain = [ + "check:ruff", + "typecheck", + "check:importable", +]} +"check:ruff" = "ruff check ." +"fix:ruff" = "ruff check --fix ." + +"check:importable" = "python -c 'import codex'" + +typecheck = { chain = [ + "typecheck:pyright", + "typecheck:mypy" +]} +"typecheck:pyright" = "pyright" +"typecheck:verify-types" = "pyright --verifytypes codex --ignoreexternal" +"typecheck:mypy" = "mypy ." + +[build-system] +requires = ["hatchling", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" + +[tool.hatch.build] +include = [ + "src/*" +] + +[tool.hatch.build.targets.wheel] +packages = ["src/codex"] + +[tool.hatch.build.targets.sdist] +# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc) +include = [ + "/*.toml", + "/*.json", + "/*.lock", + "/*.md", + "/mypy.ini", + "/noxfile.py", + "bin/*", + "examples/*", + "src/*", + "tests/*", +] + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "README.md" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] +# replace relative links with absolute links +pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' +replacement = '[\1](https://github.com/stainless-sdks/codex-python/tree/main/\g<2>)' + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "--tb=short" +xfail_strict = true +asyncio_mode = "auto" +filterwarnings = [ + "error" +] + +[tool.pyright] +# this enables practically every flag given by pyright. +# there are a couple of flags that are still disabled by +# default in strict mode as they are experimental and niche. +typeCheckingMode = "strict" +pythonVersion = "3.8" + +exclude = [ + "_dev", + ".venv", + ".nox", +] + +reportImplicitOverride = true + +reportImportCycles = false +reportPrivateUsage = false + + +[tool.ruff] +line-length = 120 +output-format = "grouped" +target-version = "py37" + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +select = [ + # isort + "I", + # bugbear rules + "B", + # remove unused imports + "F401", + # bare except statements + "E722", + # unused arguments + "ARG", + # print statements + "T201", + "T203", + # misuse of typing.TYPE_CHECKING + "TCH004", + # import rules + "TID251", +] +ignore = [ + # mutable defaults + "B006", +] +unfixable = [ + # disable auto fix for print statements + "T201", + "T203", +] + +[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" + +[tool.ruff.lint.isort] +length-sort = true +length-sort-straight = true +combine-as-imports = true +extra-standard-library = ["typing_extensions"] +known-first-party = ["codex", "tests"] + +[tool.ruff.lint.per-file-ignores] +"bin/**.py" = ["T201", "T203"] +"scripts/**.py" = ["T201", "T203"] +"tests/**.py" = ["T201", "T203"] +"examples/**.py" = ["T201", "T203"] diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 00000000..11bf46e6 --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,103 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false + +-e file:. +annotated-types==0.6.0 + # via pydantic +anyio==4.4.0 + # via codex + # via httpx +argcomplete==3.1.2 + # via nox +certifi==2023.7.22 + # via httpcore + # via httpx +colorlog==6.7.0 + # via nox +dirty-equals==0.6.0 +distlib==0.3.7 + # via virtualenv +distro==1.8.0 + # via codex +exceptiongroup==1.2.2 + # via anyio + # via pytest +filelock==3.12.4 + # via virtualenv +h11==0.14.0 + # via httpcore +httpcore==1.0.2 + # via httpx +httpx==0.28.1 + # via codex + # via respx +idna==3.4 + # via anyio + # via httpx +importlib-metadata==7.0.0 +iniconfig==2.0.0 + # via pytest +markdown-it-py==3.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +mypy==1.13.0 +mypy-extensions==1.0.0 + # via mypy +nest-asyncio==1.6.0 +nodeenv==1.8.0 + # via pyright +nox==2023.4.22 +packaging==23.2 + # via nox + # via pytest +platformdirs==3.11.0 + # via virtualenv +pluggy==1.5.0 + # via pytest +pydantic==2.10.3 + # via codex +pydantic-core==2.27.1 + # via pydantic +pygments==2.18.0 + # via rich +pyright==1.1.390 +pytest==8.3.3 + # via pytest-asyncio +pytest-asyncio==0.24.0 +python-dateutil==2.8.2 + # via time-machine +pytz==2023.3.post1 + # via dirty-equals +respx==0.22.0 +rich==13.7.1 +ruff==0.6.9 +setuptools==68.2.2 + # via nodeenv +six==1.16.0 + # via python-dateutil +sniffio==1.3.0 + # via anyio + # via codex +time-machine==2.9.0 +tomli==2.0.2 + # via mypy + # via pytest +typing-extensions==4.12.2 + # via anyio + # via codex + # via mypy + # via pydantic + # via pydantic-core + # via pyright +virtualenv==20.24.5 + # via nox +zipp==3.17.0 + # via importlib-metadata diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 00000000..fb7b045f --- /dev/null +++ b/requirements.lock @@ -0,0 +1,44 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: true +# with-sources: false +# generate-hashes: false + +-e file:. +annotated-types==0.6.0 + # via pydantic +anyio==4.4.0 + # via codex + # via httpx +certifi==2023.7.22 + # via httpcore + # via httpx +distro==1.8.0 + # via codex +exceptiongroup==1.2.2 + # via anyio +h11==0.14.0 + # via httpcore +httpcore==1.0.2 + # via httpx +httpx==0.28.1 + # via codex +idna==3.4 + # via anyio + # via httpx +pydantic==2.10.3 + # via codex +pydantic-core==2.27.1 + # via pydantic +sniffio==1.3.0 + # via anyio + # via codex +typing-extensions==4.12.2 + # via anyio + # via codex + # via pydantic + # via pydantic-core diff --git a/scripts/bootstrap b/scripts/bootstrap new file mode 100755 index 00000000..8c5c60eb --- /dev/null +++ b/scripts/bootstrap @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then + brew bundle check >/dev/null 2>&1 || { + echo "==> Installing Homebrew dependencies…" + brew bundle + } +fi + +echo "==> Installing Python dependencies…" + +# experimental uv support makes installations significantly faster +rye config --set-bool behavior.use-uv=true + +rye sync --all-features diff --git a/scripts/format b/scripts/format new file mode 100755 index 00000000..667ec2d7 --- /dev/null +++ b/scripts/format @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running formatters" +rye run format diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 00000000..6f2bb0d0 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running lints" +rye run lint + +echo "==> Making sure it imports" +rye run python -c 'import codex' + diff --git a/scripts/mock b/scripts/mock new file mode 100755 index 00000000..d2814ae6 --- /dev/null +++ b/scripts/mock @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [[ -n "$1" && "$1" != '--'* ]]; then + URL="$1" + shift +else + URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" +fi + +# Check if the URL is empty +if [ -z "$URL" ]; then + echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" + exit 1 +fi + +echo "==> Starting mock server with URL ${URL}" + +# Run prism mock on the given spec +if [ "$1" == "--daemon" ]; then + npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & + + # Wait for server to come online + echo -n "Waiting for server" + while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + echo -n "." + sleep 0.1 + done + + if grep -q "✖ fatal" ".prism.log"; then + cat .prism.log + exit 1 + fi + + echo +else + npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" +fi diff --git a/scripts/test b/scripts/test new file mode 100755 index 00000000..4fa5698b --- /dev/null +++ b/scripts/test @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +function prism_is_running() { + curl --silent "http://localhost:4010" >/dev/null 2>&1 +} + +kill_server_on_port() { + pids=$(lsof -t -i tcp:"$1" || echo "") + if [ "$pids" != "" ]; then + kill "$pids" + echo "Stopped $pids." + fi +} + +function is_overriding_api_base_url() { + [ -n "$TEST_API_BASE_URL" ] +} + +if ! is_overriding_api_base_url && ! prism_is_running ; then + # When we exit this script, make sure to kill the background mock server process + trap 'kill_server_on_port 4010' EXIT + + # Start the dev server + ./scripts/mock --daemon +fi + +if is_overriding_api_base_url ; then + echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" + echo +elif ! prism_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" + echo -e "running against your OpenAPI spec." + echo + echo -e "To run the server, pass in the path or url of your OpenAPI" + echo -e "spec to the prism command:" + echo + echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" + echo + + exit 1 +else + echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo +fi + +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 new file mode 100644 index 00000000..37b3d94f --- /dev/null +++ b/scripts/utils/ruffen-docs.py @@ -0,0 +1,167 @@ +# fork of https://github.com/asottile/blacken-docs adapted for ruff +from __future__ import annotations + +import re +import sys +import argparse +import textwrap +import contextlib +import subprocess +from typing import Match, Optional, Sequence, Generator, NamedTuple, cast + +MD_RE = re.compile( + r"(?P^(?P *)```\s*python\n)" r"(?P.*?)" r"(?P^(?P=indent)```\s*$)", + re.DOTALL | re.MULTILINE, +) +MD_PYCON_RE = re.compile( + r"(?P^(?P *)```\s*pycon\n)" r"(?P.*?)" r"(?P^(?P=indent)```.*$)", + re.DOTALL | re.MULTILINE, +) +PYCON_PREFIX = ">>> " +PYCON_CONTINUATION_PREFIX = "..." +PYCON_CONTINUATION_RE = re.compile( + rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)", +) +DEFAULT_LINE_LENGTH = 100 + + +class CodeBlockError(NamedTuple): + offset: int + exc: Exception + + +def format_str( + src: str, +) -> tuple[str, Sequence[CodeBlockError]]: + errors: list[CodeBlockError] = [] + + @contextlib.contextmanager + def _collect_error(match: Match[str]) -> Generator[None, None, None]: + try: + yield + except Exception as e: + errors.append(CodeBlockError(match.start(), e)) + + def _md_match(match: Match[str]) -> str: + code = textwrap.dedent(match["code"]) + with _collect_error(match): + code = format_code_block(code) + code = textwrap.indent(code, match["indent"]) + return f'{match["before"]}{code}{match["after"]}' + + def _pycon_match(match: Match[str]) -> str: + code = "" + fragment = cast(Optional[str], None) + + def finish_fragment() -> None: + nonlocal code + nonlocal fragment + + if fragment is not None: + with _collect_error(match): + fragment = format_code_block(fragment) + fragment_lines = fragment.splitlines() + code += f"{PYCON_PREFIX}{fragment_lines[0]}\n" + for line in fragment_lines[1:]: + # Skip blank lines to handle Black adding a blank above + # functions within blocks. A blank line would end the REPL + # continuation prompt. + # + # >>> if True: + # ... def f(): + # ... pass + # ... + if line: + code += f"{PYCON_CONTINUATION_PREFIX} {line}\n" + if fragment_lines[-1].startswith(" "): + code += f"{PYCON_CONTINUATION_PREFIX}\n" + fragment = None + + indentation = None + for line in match["code"].splitlines(): + orig_line, line = line, line.lstrip() + if indentation is None and line: + indentation = len(orig_line) - len(line) + continuation_match = PYCON_CONTINUATION_RE.match(line) + if continuation_match and fragment is not None: + fragment += line[continuation_match.end() :] + "\n" + else: + finish_fragment() + if line.startswith(PYCON_PREFIX): + fragment = line[len(PYCON_PREFIX) :] + "\n" + else: + code += orig_line[indentation:] + "\n" + finish_fragment() + return code + + 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"]}' + + src = MD_RE.sub(_md_match, src) + src = MD_PYCON_RE.sub(_md_pycon_match, src) + return src, errors + + +def format_code_block(code: str) -> str: + return subprocess.check_output( + [ + sys.executable, + "-m", + "ruff", + "format", + "--stdin-filename=script.py", + f"--line-length={DEFAULT_LINE_LENGTH}", + ], + encoding="utf-8", + input=code, + ) + + +def format_file( + filename: str, + skip_errors: bool, +) -> int: + with open(filename, encoding="UTF-8") as f: + contents = f.read() + new_contents, errors = format_str(contents) + for error in errors: + lineno = contents[: error.offset].count("\n") + 1 + print(f"{filename}:{lineno}: code block parse error {error.exc}") + if errors and not skip_errors: + return 1 + if contents != new_contents: + print(f"{filename}: Rewriting...") + with open(filename, "w", encoding="UTF-8") as f: + f.write(new_contents) + return 0 + else: + return 0 + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "-l", + "--line-length", + type=int, + default=DEFAULT_LINE_LENGTH, + ) + parser.add_argument( + "-S", + "--skip-string-normalization", + action="store_true", + ) + parser.add_argument("-E", "--skip-errors", action="store_true") + parser.add_argument("filenames", nargs="*") + args = parser.parse_args(argv) + + retv = 0 + for filename in args.filenames: + retv |= format_file(filename, skip_errors=args.skip_errors) + return retv + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/codex/__init__.py b/src/codex/__init__.py new file mode 100644 index 00000000..bdcdb71a --- /dev/null +++ b/src/codex/__init__.py @@ -0,0 +1,84 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from . import types +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes +from ._utils import file_from_path +from ._client import Codex, Client, Stream, Timeout, Transport, AsyncCodex, AsyncClient, AsyncStream, RequestOptions +from ._models import BaseModel +from ._version import __title__, __version__ +from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse +from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS +from ._exceptions import ( + APIError, + CodexError, + ConflictError, + NotFoundError, + APIStatusError, + RateLimitError, + APITimeoutError, + BadRequestError, + APIConnectionError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, + UnprocessableEntityError, + APIResponseValidationError, +) +from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._utils._logs import setup_logging as _setup_logging + +__all__ = [ + "types", + "__version__", + "__title__", + "NoneType", + "Transport", + "ProxiesTypes", + "NotGiven", + "NOT_GIVEN", + "Omit", + "CodexError", + "APIError", + "APIStatusError", + "APITimeoutError", + "APIConnectionError", + "APIResponseValidationError", + "BadRequestError", + "AuthenticationError", + "PermissionDeniedError", + "NotFoundError", + "ConflictError", + "UnprocessableEntityError", + "RateLimitError", + "InternalServerError", + "Timeout", + "RequestOptions", + "Client", + "AsyncClient", + "Stream", + "AsyncStream", + "Codex", + "AsyncCodex", + "file_from_path", + "BaseModel", + "DEFAULT_TIMEOUT", + "DEFAULT_MAX_RETRIES", + "DEFAULT_CONNECTION_LIMITS", + "DefaultHttpxClient", + "DefaultAsyncHttpxClient", +] + +_setup_logging() + +# Update the __module__ attribute for exported symbols so that +# error messages point to this module instead of the module +# it was originally defined in, e.g. +# codex._exceptions.NotFoundError -> codex.NotFoundError +__locals = locals() +for __name in __all__: + if not __name.startswith("__"): + try: + __locals[__name].__module__ = "codex" + except (TypeError, AttributeError): + # Some of our exported symbols are builtins which we can't set attributes for. + pass diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py new file mode 100644 index 00000000..9090e758 --- /dev/null +++ b/src/codex/_base_client.py @@ -0,0 +1,2051 @@ +from __future__ import annotations + +import sys +import json +import time +import uuid +import email +import asyncio +import inspect +import logging +import platform +import warnings +import email.utils +from types import TracebackType +from random import random +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Type, + Union, + Generic, + Mapping, + TypeVar, + Iterable, + Iterator, + Optional, + Generator, + AsyncIterator, + cast, + overload, +) +from typing_extensions import Literal, override, get_origin + +import anyio +import httpx +import distro +import pydantic +from httpx import URL, Limits +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, + Headers, + Timeout, + NotGiven, + ResponseT, + Transport, + AnyMapping, + PostParser, + ProxiesTypes, + RequestFiles, + HttpxSendArgs, + AsyncTransport, + RequestOptions, + HttpxRequestFiles, + ModelBuilderProtocol, +) +from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping +from ._compat import model_copy, model_dump +from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type +from ._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + extract_response_type, +) +from ._constants import ( + DEFAULT_TIMEOUT, + MAX_RETRY_DELAY, + DEFAULT_MAX_RETRIES, + INITIAL_RETRY_DELAY, + RAW_RESPONSE_HEADER, + OVERRIDE_CAST_TO_HEADER, + DEFAULT_CONNECTION_LIMITS, +) +from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder +from ._exceptions import ( + APIStatusError, + APITimeoutError, + APIConnectionError, + APIResponseValidationError, +) + +log: logging.Logger = logging.getLogger(__name__) + +# TODO: make base page type vars covariant +SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]") +AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]") + + +_T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) + +_StreamT = TypeVar("_StreamT", bound=Stream[Any]) +_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) + +if TYPE_CHECKING: + from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT +else: + try: + from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + except ImportError: + # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366 + HTTPX_DEFAULT_TIMEOUT = Timeout(5.0) + + +class PageInfo: + """Stores the necessary information to build the request to retrieve the next page. + + Either `url` or `params` must be set. + """ + + url: URL | NotGiven + params: Query | NotGiven + + @overload + def __init__( + self, + *, + url: URL, + ) -> None: ... + + @overload + def __init__( + self, + *, + params: Query, + ) -> None: ... + + def __init__( + self, + *, + url: URL | NotGiven = NOT_GIVEN, + params: Query | NotGiven = NOT_GIVEN, + ) -> None: + self.url = url + self.params = params + + @override + def __repr__(self) -> str: + if self.url: + return f"{self.__class__.__name__}(url={self.url})" + return f"{self.__class__.__name__}(params={self.params})" + + +class BasePage(GenericModel, Generic[_T]): + """ + Defines the core interface for pagination. + + Type Args: + ModelT: The pydantic model that represents an item in the response. + + Methods: + has_next_page(): Check if there is another page available + next_page_info(): Get the necessary information to make a request for the next page + """ + + _options: FinalRequestOptions = PrivateAttr() + _model: Type[_T] = PrivateAttr() + + def has_next_page(self) -> bool: + items = self._get_page_items() + if not items: + return False + return self.next_page_info() is not None + + def next_page_info(self) -> Optional[PageInfo]: ... + + def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body] + ... + + def _params_from_url(self, url: URL) -> httpx.QueryParams: + # TODO: do we have to preprocess params here? + return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params) + + def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: + options = model_copy(self._options) + options._strip_raw_response_header() + + if not isinstance(info.params, NotGiven): + options.params = {**options.params, **info.params} + return options + + if not isinstance(info.url, NotGiven): + params = self._params_from_url(info.url) + url = info.url.copy_with(params=params) + options.params = dict(url.params) + options.url = str(url) + return options + + raise ValueError("Unexpected PageInfo state") + + +class BaseSyncPage(BasePage[_T], Generic[_T]): + _client: SyncAPIClient = pydantic.PrivateAttr() + + def _set_private_attributes( + self, + client: SyncAPIClient, + model: Type[_T], + options: FinalRequestOptions, + ) -> None: + self._model = model + self._client = client + self._options = options + + # Pydantic uses a custom `__iter__` method to support casting BaseModels + # to dictionaries. e.g. dict(model). + # As we want to support `for item in page`, this is inherently incompatible + # with the default pydantic behaviour. It is not possible to support both + # use cases at once. Fortunately, this is not a big deal as all other pydantic + # methods should continue to work as expected as there is an alternative method + # to cast a model to a dictionary, model.dict(), which is used internally + # by pydantic. + def __iter__(self) -> Iterator[_T]: # type: ignore + for page in self.iter_pages(): + for item in page._get_page_items(): + yield item + + def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]: + page = self + while True: + yield page + if page.has_next_page(): + page = page.get_next_page() + else: + return + + def get_next_page(self: SyncPageT) -> SyncPageT: + info = self.next_page_info() + if not info: + raise RuntimeError( + "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." + ) + + options = self._info_to_options(info) + return self._client._request_api_list(self._model, page=self.__class__, options=options) + + +class AsyncPaginator(Generic[_T, AsyncPageT]): + def __init__( + self, + client: AsyncAPIClient, + options: FinalRequestOptions, + page_cls: Type[AsyncPageT], + model: Type[_T], + ) -> None: + self._model = model + self._client = client + self._options = options + self._page_cls = page_cls + + def __await__(self) -> Generator[Any, None, AsyncPageT]: + return self._get_page().__await__() + + async def _get_page(self) -> AsyncPageT: + def _parser(resp: AsyncPageT) -> AsyncPageT: + resp._set_private_attributes( + model=self._model, + options=self._options, + client=self._client, + ) + return resp + + self._options.post_parser = _parser + + return await self._client.request(self._page_cls, self._options) + + async def __aiter__(self) -> AsyncIterator[_T]: + # https://github.com/microsoft/pyright/issues/3464 + page = cast( + AsyncPageT, + await self, # type: ignore + ) + async for item in page: + yield item + + +class BaseAsyncPage(BasePage[_T], Generic[_T]): + _client: AsyncAPIClient = pydantic.PrivateAttr() + + def _set_private_attributes( + self, + model: Type[_T], + client: AsyncAPIClient, + options: FinalRequestOptions, + ) -> None: + self._model = model + self._client = client + self._options = options + + async def __aiter__(self) -> AsyncIterator[_T]: + async for page in self.iter_pages(): + for item in page._get_page_items(): + yield item + + async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]: + page = self + while True: + yield page + if page.has_next_page(): + page = await page.get_next_page() + else: + return + + async def get_next_page(self: AsyncPageT) -> AsyncPageT: + info = self.next_page_info() + if not info: + raise RuntimeError( + "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." + ) + + options = self._info_to_options(info) + return await self._client._request_api_list(self._model, page=self.__class__, options=options) + + +_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient]) +_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]]) + + +class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): + _client: _HttpxClientT + _version: str + _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 + + def __init__( + self, + *, + version: str, + base_url: str | URL, + _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: + self._version = version + 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 + self._idempotency_header = None + self._platform: Platform | None = None + + if max_retries is None: # pyright: ignore[reportUnnecessaryComparison] + raise TypeError( + "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `codex.DEFAULT_MAX_RETRIES`" + ) + + def _enforce_trailing_slash(self, url: URL) -> URL: + if url.raw_path.endswith(b"/"): + return url + return url.copy_with(raw_path=url.raw_path + b"/") + + def _make_status_error_from_response( + self, + response: httpx.Response, + ) -> APIStatusError: + if response.is_closed and not response.is_stream_consumed: + # We can't read the response body as it has been closed + # before it was read. This can happen if an event hook + # raises a status error. + body = None + err_msg = f"Error code: {response.status_code}" + else: + err_text = response.text.strip() + body = err_text + + try: + body = json.loads(err_text) + err_msg = f"Error code: {response.status_code} - {body}" + except Exception: + err_msg = err_text or f"Error code: {response.status_code}" + + return self._make_status_error(err_msg, body=body, response=response) + + def _make_status_error( + self, + err_msg: str, + *, + body: object, + response: httpx.Response, + ) -> _exceptions.APIStatusError: + raise NotImplementedError() + + 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) + + # headers are case-insensitive while dictionaries are not. + 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() + + # Don't set the retry count header if it was already set or removed by the caller. We check + # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. + if "x-stainless-retry-count" not in (header.lower() for header in custom_headers): + headers["x-stainless-retry-count"] = str(retries_taken) + + return headers + + def _prepare_url(self, url: str) -> URL: + """ + Merge a URL argument together with any 'base_url' on the client, + to create the URL used for the outgoing request. + """ + # Copied from httpx's `_merge_url` method. + merge_url = URL(url) + if merge_url.is_relative_url: + merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/") + return self.base_url.copy_with(raw_path=merge_raw_path) + + return merge_url + + def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: + return SSEDecoder() + + 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)) + + kwargs: dict[str, Any] = {} + + json_data = options.json_data + if options.extra_json is not None: + if json_data is None: + json_data = cast(Body, options.extra_json) + elif is_mapping(json_data): + json_data = _merge_mappings(json_data, options.extra_json) + else: + raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") + + 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 + + # If the given Content-Type header is multipart/form-data then it + # has to be removed so that httpx can generate the header with + # additional information for us as it has to be in this form + # for the server to be able to correctly parse the request: + # multipart/form-data; boundary=---abc-- + if content_type is not None and content_type.startswith("multipart/form-data"): + if "boundary" not in content_type: + # only remove the header if the boundary hasn't been explicitly set + # as the caller doesn't want httpx to come up with their own boundary + headers.pop("Content-Type") + + # As we are now sending multipart/form-data instead of application/json + # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding + if json_data: + if not is_dict(json_data): + raise TypeError( + f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead." + ) + kwargs["data"] = self._serialize_multipartform(json_data) + + # httpx determines whether or not to send a "multipart/form-data" + # request based on the truthiness of the "files" argument. + # This gets around that issue by generating a dict value that + # evaluates to true. + # + # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186 + 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("_", "-")} + + # 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=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, + ) + + def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]: + items = self.qs.stringify_items( + # TODO: type ignore is required as stringify_items is well typed but we can't be + # well typed without heavy validation. + data, # type: ignore + array_format="brackets", + ) + serialized: dict[str, object] = {} + for key, value in items: + existing = serialized.get(key) + + if not existing: + serialized[key] = value + continue + + # If a value has already been set for this key then that + # means we're sending data like `array[]=[1, 2, 3]` and we + # need to tell httpx that we want to send multiple values with + # the same key which is done by using a list or a tuple. + # + # Note: 2d arrays should never result in the same key at both + # levels so it's safe to assume that if the value is a list, + # it was because we changed it to be a list. + if is_list(existing): + existing.append(value) + else: + serialized[key] = [existing, value] + + return serialized + + def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]: + if not is_given(options.headers): + return cast_to + + # make a copy of the headers so we don't mutate user-input + headers = dict(options.headers) + + # 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) + if is_given(override_cast_to): + options.headers = headers + return cast(Type[ResponseT], override_cast_to) + + return cast_to + + def _should_stream_response_body(self, request: httpx.Request) -> bool: + return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return] + + def _process_response_data( + self, + *, + data: object, + cast_to: type[ResponseT], + response: httpx.Response, + ) -> ResponseT: + if data is None: + return cast(ResponseT, None) + + if cast_to is object: + return cast(ResponseT, data) + + try: + if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol): + return cast(ResponseT, cast_to.build(response=response, data=data)) + + if self._strict_response_validation: + return cast(ResponseT, validate_type(type_=cast_to, value=data)) + + return cast(ResponseT, construct_type(type_=cast_to, value=data)) + except pydantic.ValidationError as err: + raise APIResponseValidationError(response=response, body=data) from err + + @property + def qs(self) -> Querystring: + return Querystring() + + @property + def custom_auth(self) -> httpx.Auth | None: + return None + + @property + def auth_headers(self) -> dict[str, str]: + return {} + + @property + def default_headers(self) -> dict[str, str | Omit]: + return { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": self.user_agent, + **self.platform_headers(), + **self.auth_headers, + **self._custom_headers, + } + + @property + def default_query(self) -> dict[str, object]: + return { + **self._custom_query, + } + + def _validate_headers( + self, + headers: Headers, # noqa: ARG002 + custom_headers: Headers, # noqa: ARG002 + ) -> None: + """Validate the given default headers and custom headers. + + Does nothing by default. + """ + return + + @property + def user_agent(self) -> str: + return f"{self.__class__.__name__}/Python {self._version}" + + @property + def base_url(self) -> URL: + return self._base_url + + @base_url.setter + def base_url(self, url: URL | str) -> None: + self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url)) + + def platform_headers(self) -> Dict[str, str]: + # the actual implementation is in a separate `lru_cache` decorated + # function because adding `lru_cache` to methods will leak memory + # https://github.com/python/cpython/issues/88476 + return platform_headers(self._version, platform=self._platform) + + def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None: + """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified. + + About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax + """ + if response_headers is None: + return None + + # First, try the non-standard `retry-after-ms` header for milliseconds, + # which is more precise than integer-seconds `retry-after` + try: + retry_ms_header = response_headers.get("retry-after-ms", None) + return float(retry_ms_header) / 1000 + except (TypeError, ValueError): + pass + + # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats). + retry_header = response_headers.get("retry-after") + try: + # note: the spec indicates that this should only ever be an integer + # but if someone sends a float there's no reason for us to not respect it + return float(retry_header) + except (TypeError, ValueError): + pass + + # Last, try parsing `retry-after` as a date. + retry_date_tuple = email.utils.parsedate_tz(retry_header) + if retry_date_tuple is None: + return None + + retry_date = email.utils.mktime_tz(retry_date_tuple) + return float(retry_date - time.time()) + + def _calculate_retry_timeout( + self, + remaining_retries: int, + options: FinalRequestOptions, + response_headers: Optional[httpx.Headers] = None, + ) -> float: + max_retries = options.get_max_retries(self.max_retries) + + # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + retry_after = self._parse_retry_after_header(response_headers) + if retry_after is not None and 0 < retry_after <= 60: + return retry_after + + # 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) + + # Apply some jitter, plus-or-minus half a second. + jitter = 1 - 0.25 * random() + timeout = sleep_seconds * jitter + return timeout if timeout >= 0 else 0 + + def _should_retry(self, response: httpx.Response) -> bool: + # Note: this is not a standard header + should_retry_header = response.headers.get("x-should-retry") + + # If the server explicitly says whether or not to retry, obey. + if should_retry_header == "true": + log.debug("Retrying as header `x-should-retry` is set to `true`") + return True + if should_retry_header == "false": + log.debug("Not retrying as header `x-should-retry` is set to `false`") + return False + + # Retry on request timeouts. + if response.status_code == 408: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry on lock timeouts. + if response.status_code == 409: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry on rate limits. + if response.status_code == 429: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry internal errors. + if response.status_code >= 500: + log.debug("Retrying due to status code %i", response.status_code) + return True + + log.debug("Not retrying") + return False + + def _idempotency_key(self) -> str: + return f"stainless-python-retry-{uuid.uuid4()}" + + +class _DefaultHttpxClient(httpx.Client): + 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: + DefaultHttpxClient = httpx.Client + """An alias to `httpx.Client` that provides the same defaults that this SDK + uses internally. + + This is useful because overriding the `http_client` with your own instance of + `httpx.Client` will result in httpx's defaults being used, not ours. + """ +else: + DefaultHttpxClient = _DefaultHttpxClient + + +class SyncHttpxClientWrapper(DefaultHttpxClient): + def __del__(self) -> None: + if self.is_closed: + return + + try: + self.close() + except Exception: + pass + + +class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]): + _client: httpx.Client + _default_stream_cls: type[Stream[Any]] | None = None + + def __init__( + self, + *, + 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, + 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: + kwargs: dict[str, Any] = {} + 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: + kwargs["transport"] = transport + 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: + kwargs["proxies"] = proxies + 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. + # + # note: there is an edge case here where the user passes in a client + # where they've explicitly set the timeout to match the default timeout + # as this check is structural, meaning that we'll think they didn't + # pass in a timeout and will ignore it + if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: + timeout = http_client.timeout + else: + timeout = DEFAULT_TIMEOUT + + if http_client is not None and not isinstance(http_client, httpx.Client): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}" + ) + + 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, + _strict_response_validation=_strict_response_validation, + ) + self._client = http_client or SyncHttpxClientWrapper( + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + limits=limits, + follow_redirects=True, + **kwargs, # type: ignore + ) + + def is_closed(self) -> bool: + return self._client.is_closed + + def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + # If an error is thrown while constructing a client, self._client + # may not be present + if hasattr(self, "_client"): + self._client.close() + + def __enter__(self: _T) -> _T: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def _prepare_options( + self, + options: FinalRequestOptions, # noqa: ARG002 + ) -> FinalRequestOptions: + """Hook for mutating the given options""" + return options + + def _prepare_request( + self, + request: httpx.Request, # noqa: ARG002 + ) -> None: + """This method is used as a callback for mutating the `Request` object + after it has been constructed. + This is useful for cases where you want to add certain headers based off of + the request properties, e.g. `url`, `method` etc. + """ + return None + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + remaining_retries: Optional[int] = None, + *, + stream: Literal[True], + stream_cls: Type[_StreamT], + ) -> _StreamT: ... + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + remaining_retries: Optional[int] = None, + *, + stream: Literal[False] = False, + ) -> 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: ... + + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + remaining_retries: Optional[int] = None, + *, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + if remaining_retries is not None: + retries_taken = options.get_max_retries(self.max_retries) - remaining_retries + else: + retries_taken = 0 + + return self._request( + cast_to=cast_to, + options=options, + stream=stream, + stream_cls=stream_cls, + retries_taken=retries_taken, + ) + + def _request( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + retries_taken: int, + stream: bool, + stream_cls: type[_StreamT] | None, + ) -> ResponseT | _StreamT: + # create a copy of the options we were given so that if the + # options are mutated later & we then retry, the retries are + # given the original options + input_options = model_copy(options) + + cast_to = self._maybe_override_cast_to(cast_to, options) + options = self._prepare_options(options) + + remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) + + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth + + log.debug("Sending HTTP Request: %s %s", request.method, request.url) + + 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: + return self._retry_request( + input_options, + cast_to, + retries_taken=retries_taken, + stream=stream, + stream_cls=stream_cls, + response_headers=None, + ) + + 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: + return self._retry_request( + input_options, + cast_to, + retries_taken=retries_taken, + stream=stream, + stream_cls=stream_cls, + response_headers=None, + ) + + 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() + return self._retry_request( + input_options, + cast_to, + retries_taken=retries_taken, + response_headers=err.response.headers, + stream=stream, + stream_cls=stream_cls, + ) + + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() + + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None + + 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], + *, + retries_taken: int, + response_headers: httpx.Headers | None, + stream: bool, + stream_cls: type[_StreamT] | None, + ) -> ResponseT | _StreamT: + remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + if remaining_retries == 1: + log.debug("1 retry left") + else: + log.debug("%i retries left", remaining_retries) + + timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + log.info("Retrying request to %s in %f seconds", options.url, timeout) + + # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a + # different thread if necessary. + time.sleep(timeout) + + return self._request( + options=options, + cast_to=cast_to, + retries_taken=retries_taken + 1, + stream=stream, + stream_cls=stream_cls, + ) + + def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + 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 not issubclass(origin, APIResponse): + raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + ResponseT, + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = APIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + 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) + + return api_response.parse() + + def _request_api_list( + self, + model: Type[object], + page: Type[SyncPageT], + options: FinalRequestOptions, + ) -> SyncPageT: + def _parser(resp: SyncPageT) -> SyncPageT: + resp._set_private_attributes( + client=self, + model=model, + options=options, + ) + return resp + + options.post_parser = _parser + + return self.request(page, options, stream=False) + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_StreamT], + ) -> _StreamT: ... + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + opts = FinalRequestOptions.construct(method="get", url=path, **options) + # cast is required because mypy complains about returning Any even though + # it understands the type variables + return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: Literal[True], + stream_cls: type[_StreamT], + ) -> _StreamT: ... + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: bool, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + opts = FinalRequestOptions.construct( + method="post", url=path, json_data=body, files=to_httpx_files(files), **options + ) + return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) + + def patch( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + return self.request(cast_to, opts) + + def put( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + opts = FinalRequestOptions.construct( + method="put", url=path, json_data=body, files=to_httpx_files(files), **options + ) + return self.request(cast_to, opts) + + def delete( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + return self.request(cast_to, opts) + + def get_api_list( + self, + path: str, + *, + model: Type[object], + page: Type[SyncPageT], + body: Body | None = None, + options: RequestOptions = {}, + method: str = "get", + ) -> SyncPageT: + opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options) + return self._request_api_list(model, page, opts) + + +class _DefaultAsyncHttpxClient(httpx.AsyncClient): + 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 + uses internally. + + 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. + """ +else: + DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + + +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()) + except Exception: + pass + + +class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]): + _client: httpx.AsyncClient + _default_stream_cls: type[AsyncStream[Any]] | None = None + + def __init__( + self, + *, + version: str, + 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, + http_client: httpx.AsyncClient | None = None, + custom_headers: Mapping[str, str] | None = None, + custom_query: Mapping[str, object] | None = None, + ) -> None: + kwargs: dict[str, Any] = {} + 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: + kwargs["transport"] = transport + 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: + kwargs["proxies"] = proxies + 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. + # + # note: there is an edge case here where the user passes in a client + # where they've explicitly set the timeout to match the default timeout + # as this check is structural, meaning that we'll think they didn't + # pass in a timeout and will ignore it + if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: + timeout = http_client.timeout + else: + timeout = DEFAULT_TIMEOUT + + if http_client is not None and not isinstance(http_client, httpx.AsyncClient): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}" + ) + + 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, + _strict_response_validation=_strict_response_validation, + ) + self._client = http_client or AsyncHttpxClientWrapper( + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + limits=limits, + follow_redirects=True, + **kwargs, # type: ignore + ) + + def is_closed(self) -> bool: + return self._client.is_closed + + async def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + await self._client.aclose() + + async def __aenter__(self: _T) -> _T: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def _prepare_options( + self, + options: FinalRequestOptions, # noqa: ARG002 + ) -> FinalRequestOptions: + """Hook for mutating the given options""" + return options + + async def _prepare_request( + self, + request: httpx.Request, # noqa: ARG002 + ) -> None: + """This method is used as a callback for mutating the `Request` object + after it has been constructed. + This is useful for cases where you want to add certain headers based off of + the request properties, e.g. `url`, `method` etc. + """ + return None + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[False] = False, + remaining_retries: Optional[int] = None, + ) -> ResponseT: ... + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + remaining_retries: Optional[int] = None, + ) -> _AsyncStreamT: ... + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + remaining_retries: Optional[int] = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + remaining_retries: Optional[int] = None, + ) -> ResponseT | _AsyncStreamT: + if remaining_retries is not None: + retries_taken = options.get_max_retries(self.max_retries) - remaining_retries + else: + retries_taken = 0 + + return await self._request( + cast_to=cast_to, + options=options, + stream=stream, + stream_cls=stream_cls, + retries_taken=retries_taken, + ) + + async def _request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool, + stream_cls: type[_AsyncStreamT] | None, + retries_taken: int, + ) -> ResponseT | _AsyncStreamT: + if self._platform is None: + # `get_platform` can make blocking IO calls so we + # execute it earlier while we are in an async context + self._platform = await asyncify(get_platform)() + + # 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) + + cast_to = self._maybe_override_cast_to(cast_to, options) + options = await self._prepare_options(options) + + remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) + + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth + + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + return await self._retry_request( + input_options, + cast_to, + retries_taken=retries_taken, + stream=stream, + stream_cls=stream_cls, + response_headers=None, + ) + + 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: + return await self._retry_request( + input_options, + cast_to, + retries_taken=retries_taken, + stream=stream, + stream_cls=stream_cls, + response_headers=None, + ) + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + 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() + return await self._retry_request( + input_options, + cast_to, + retries_taken=retries_taken, + response_headers=err.response.headers, + stream=stream, + stream_cls=stream_cls, + ) + + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() + + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None + + 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], + *, + retries_taken: int, + response_headers: httpx.Headers | None, + stream: bool, + stream_cls: type[_AsyncStreamT] | None, + ) -> ResponseT | _AsyncStreamT: + remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + if remaining_retries == 1: + log.debug("1 retry left") + else: + log.debug("%i retries left", remaining_retries) + + timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + log.info("Retrying request to %s in %f seconds", options.url, timeout) + + await anyio.sleep(timeout) + + return await self._request( + options=options, + cast_to=cast_to, + retries_taken=retries_taken + 1, + stream=stream, + stream_cls=stream_cls, + ) + + async def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + 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 not issubclass(origin, AsyncAPIResponse): + raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + "ResponseT", + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = AsyncAPIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + 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) + + return await api_response.parse() + + def _request_api_list( + self, + model: Type[_T], + page: Type[AsyncPageT], + options: FinalRequestOptions, + ) -> AsyncPaginator[_T, AsyncPageT]: + return AsyncPaginator(client=self, options=options, page_cls=page, model=model) + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + opts = FinalRequestOptions.construct(method="get", url=path, **options) + return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + opts = FinalRequestOptions.construct( + method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options + ) + return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) + + async def patch( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + return await self.request(cast_to, opts) + + async def put( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + opts = FinalRequestOptions.construct( + method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options + ) + return await self.request(cast_to, opts) + + async def delete( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + return await self.request(cast_to, opts) + + def get_api_list( + self, + path: str, + *, + model: Type[_T], + page: Type[AsyncPageT], + body: Body | None = None, + options: RequestOptions = {}, + method: str = "get", + ) -> AsyncPaginator[_T, AsyncPageT]: + opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options) + return self._request_api_list(model, page, opts) + + +def make_request_options( + *, + query: Query | None = None, + extra_headers: Headers | None = None, + 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, +) -> RequestOptions: + """Create a dict of type RequestOptions without keys of NotGiven values.""" + options: RequestOptions = {} + if extra_headers is not None: + options["headers"] = extra_headers + + if extra_body is not None: + options["extra_json"] = cast(AnyMapping, extra_body) + + if query is not None: + options["params"] = query + + if extra_query is not None: + options["params"] = {**options.get("params", {}), **extra_query} + + if not isinstance(timeout, NotGiven): + options["timeout"] = timeout + + if idempotency_key is not None: + options["idempotency_key"] = idempotency_key + + if is_given(post_parser): + # internal + options["post_parser"] = post_parser # type: ignore + + return options + + +class ForceMultipartDict(Dict[str, None]): + def __bool__(self) -> bool: + return True + + +class OtherPlatform: + def __init__(self, name: str) -> None: + self.name = name + + @override + def __str__(self) -> str: + return f"Other:{self.name}" + + +Platform = Union[ + OtherPlatform, + Literal[ + "MacOS", + "Linux", + "Windows", + "FreeBSD", + "OpenBSD", + "iOS", + "Android", + "Unknown", + ], +] + + +def get_platform() -> Platform: + try: + system = platform.system().lower() + platform_name = platform.platform().lower() + except Exception: + return "Unknown" + + if "iphone" in platform_name or "ipad" in platform_name: + # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7 + # system is Darwin and platform_name is a string like: + # - Darwin-21.6.0-iPhone12,1-64bit + # - Darwin-21.6.0-iPad7,11-64bit + return "iOS" + + if system == "darwin": + return "MacOS" + + if system == "windows": + return "Windows" + + if "android" in platform_name: + # Tested using Pydroid 3 + # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc' + return "Android" + + if system == "linux": + # https://distro.readthedocs.io/en/latest/#distro.id + distro_id = distro.id() + if distro_id == "freebsd": + return "FreeBSD" + + if distro_id == "openbsd": + return "OpenBSD" + + return "Linux" + + if platform_name: + return OtherPlatform(platform_name) + + return "Unknown" + + +@lru_cache(maxsize=None) +def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]: + return { + "X-Stainless-Lang": "python", + "X-Stainless-Package-Version": version, + "X-Stainless-OS": str(platform or get_platform()), + "X-Stainless-Arch": str(get_architecture()), + "X-Stainless-Runtime": get_python_runtime(), + "X-Stainless-Runtime-Version": get_python_version(), + } + + +class OtherArch: + def __init__(self, name: str) -> None: + self.name = name + + @override + def __str__(self) -> str: + return f"other:{self.name}" + + +Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]] + + +def get_python_runtime() -> str: + try: + return platform.python_implementation() + except Exception: + return "unknown" + + +def get_python_version() -> str: + try: + return platform.python_version() + except Exception: + return "unknown" + + +def get_architecture() -> Arch: + try: + machine = platform.machine().lower() + except Exception: + return "unknown" + + if machine in ("arm64", "aarch64"): + return "arm64" + + # TODO: untested + if machine == "arm": + return "arm" + + if machine == "x86_64": + return "x64" + + # TODO: untested + if sys.maxsize <= 2**32: + return "x32" + + if machine: + return OtherArch(machine) + + return "unknown" + + +def _merge_mappings( + obj1: Mapping[_T_co, Union[_T, Omit]], + obj2: Mapping[_T_co, Union[_T, Omit]], +) -> Dict[_T_co, _T]: + """Merge two mappings of the same type, removing any values that are instances of `Omit`. + + In cases with duplicate keys the second mapping takes precedence. + """ + merged = {**obj1, **obj2} + return {key: value for key, value in merged.items() if not isinstance(value, Omit)} diff --git a/src/codex/_client.py b/src/codex/_client.py new file mode 100644 index 00000000..bda9e89c --- /dev/null +++ b/src/codex/_client.py @@ -0,0 +1,518 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, Union, Mapping +from typing_extensions import Self, override + +import httpx + +from . import _exceptions +from ._qs import Querystring +from ._types import ( + NOT_GIVEN, + Omit, + Timeout, + NotGiven, + Transport, + ProxiesTypes, + RequestOptions, +) +from ._utils import ( + is_given, + get_async_library, +) +from ._version import __version__ +from .resources import health +from ._streaming import Stream as Stream, AsyncStream as AsyncStream +from ._exceptions import CodexError, APIStatusError +from ._base_client import ( + DEFAULT_MAX_RETRIES, + SyncAPIClient, + AsyncAPIClient, +) +from .resources.users import users +from .resources.projects import projects +from .resources.organizations import organizations + +__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Codex", "AsyncCodex", "Client", "AsyncClient"] + + +class Codex(SyncAPIClient): + health: health.HealthResource + organizations: organizations.OrganizationsResource + users: users.UsersResource + projects: projects.ProjectsResource + with_raw_response: CodexWithRawResponse + with_streaming_response: CodexWithStreamedResponse + + # client options + bearer_token: str + api_key: str + access_key: str + + def __init__( + self, + *, + bearer_token: str | None = None, + api_key: str | None = None, + access_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: Union[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, + # Configure a custom httpx client. + # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. + # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details. + http_client: httpx.Client | None = None, + # Enable or disable schema validation for data returned by the API. + # When enabled an error APIResponseValidationError is raised + # if the API responds with invalid data for the expected schema. + # + # This parameter may be removed or changed in the future. + # If you rely on this feature, please open a GitHub issue + # outlining your use-case to help us decide if it should be + # part of our public interface in the future. + _strict_response_validation: bool = False, + ) -> None: + """Construct a new synchronous codex client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `bearer_token` from `BEARER_TOKEN` + - `api_key` from `AUTHENTICATED_API_KEY` + - `access_key` from `PUBLIC_ACCESS_KEY` + """ + if bearer_token is None: + bearer_token = os.environ.get("BEARER_TOKEN") + if bearer_token is None: + raise CodexError( + "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable" + ) + self.bearer_token = bearer_token + + if api_key is None: + api_key = os.environ.get("AUTHENTICATED_API_KEY") + if api_key is None: + raise CodexError( + "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable" + ) + self.api_key = api_key + + if access_key is None: + access_key = os.environ.get("PUBLIC_ACCESS_KEY") + if access_key is None: + raise CodexError( + "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable" + ) + self.access_key = access_key + + if base_url is None: + base_url = os.environ.get("CODEX_BASE_URL") + if base_url is None: + base_url = f"https://localhost:8080/test-api" + + super().__init__( + version=__version__, + base_url=base_url, + max_retries=max_retries, + timeout=timeout, + http_client=http_client, + custom_headers=default_headers, + custom_query=default_query, + _strict_response_validation=_strict_response_validation, + ) + + self.health = health.HealthResource(self) + self.organizations = organizations.OrganizationsResource(self) + self.users = users.UsersResource(self) + self.projects = projects.ProjectsResource(self) + self.with_raw_response = CodexWithRawResponse(self) + self.with_streaming_response = CodexWithStreamedResponse(self) + + @property + @override + def qs(self) -> Querystring: + return Querystring(array_format="comma") + + @property + @override + def auth_headers(self) -> dict[str, str]: + if self._http_bearer: + return self._http_bearer + if self._authenticated_api_key: + return self._authenticated_api_key + if self._public_access_key: + return self._public_access_key + return {} + + @property + def _http_bearer(self) -> dict[str, str]: + bearer_token = self.bearer_token + return {"Authorization": f"Bearer {bearer_token}"} + + @property + def _authenticated_api_key(self) -> dict[str, str]: + api_key = self.api_key + return {"X-API-Key": api_key} + + @property + def _public_access_key(self) -> dict[str, str]: + access_key = self.access_key + return {"X-Access-Key": access_key} + + @property + @override + def default_headers(self) -> dict[str, str | Omit]: + return { + **super().default_headers, + "X-Stainless-Async": "false", + **self._custom_headers, + } + + def copy( + self, + *, + bearer_token: str | None = None, + api_key: str | None = None, + access_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + http_client: httpx.Client | None = None, + 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, + set_default_query: Mapping[str, object] | None = None, + _extra_kwargs: Mapping[str, Any] = {}, + ) -> Self: + """ + Create a new client instance re-using the same options given to the current client with optional overriding. + """ + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + + http_client = http_client or self._client + return self.__class__( + bearer_token=bearer_token or self.bearer_token, + api_key=api_key or self.api_key, + access_key=access_key or self.access_key, + base_url=base_url or self.base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + **_extra_kwargs, + ) + + # Alias for `copy` for nicer inline usage, e.g. + # client.with_options(timeout=10).foo.create(...) + with_options = copy + + @override + def _make_status_error( + self, + err_msg: str, + *, + body: object, + response: httpx.Response, + ) -> APIStatusError: + if response.status_code == 400: + return _exceptions.BadRequestError(err_msg, response=response, body=body) + + if response.status_code == 401: + return _exceptions.AuthenticationError(err_msg, response=response, body=body) + + if response.status_code == 403: + return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) + + if response.status_code == 404: + return _exceptions.NotFoundError(err_msg, response=response, body=body) + + if response.status_code == 409: + return _exceptions.ConflictError(err_msg, response=response, body=body) + + if response.status_code == 422: + return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) + + if response.status_code == 429: + return _exceptions.RateLimitError(err_msg, response=response, body=body) + + if response.status_code >= 500: + return _exceptions.InternalServerError(err_msg, response=response, body=body) + return APIStatusError(err_msg, response=response, body=body) + + +class AsyncCodex(AsyncAPIClient): + health: health.AsyncHealthResource + organizations: organizations.AsyncOrganizationsResource + users: users.AsyncUsersResource + projects: projects.AsyncProjectsResource + with_raw_response: AsyncCodexWithRawResponse + with_streaming_response: AsyncCodexWithStreamedResponse + + # client options + bearer_token: str + api_key: str + access_key: str + + def __init__( + self, + *, + bearer_token: str | None = None, + api_key: str | None = None, + access_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: Union[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, + # Configure a custom httpx client. + # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. + # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details. + http_client: httpx.AsyncClient | None = None, + # Enable or disable schema validation for data returned by the API. + # When enabled an error APIResponseValidationError is raised + # if the API responds with invalid data for the expected schema. + # + # This parameter may be removed or changed in the future. + # If you rely on this feature, please open a GitHub issue + # outlining your use-case to help us decide if it should be + # part of our public interface in the future. + _strict_response_validation: bool = False, + ) -> None: + """Construct a new async codex client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `bearer_token` from `BEARER_TOKEN` + - `api_key` from `AUTHENTICATED_API_KEY` + - `access_key` from `PUBLIC_ACCESS_KEY` + """ + if bearer_token is None: + bearer_token = os.environ.get("BEARER_TOKEN") + if bearer_token is None: + raise CodexError( + "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable" + ) + self.bearer_token = bearer_token + + if api_key is None: + api_key = os.environ.get("AUTHENTICATED_API_KEY") + if api_key is None: + raise CodexError( + "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable" + ) + self.api_key = api_key + + if access_key is None: + access_key = os.environ.get("PUBLIC_ACCESS_KEY") + if access_key is None: + raise CodexError( + "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable" + ) + self.access_key = access_key + + if base_url is None: + base_url = os.environ.get("CODEX_BASE_URL") + if base_url is None: + base_url = f"https://localhost:8080/test-api" + + super().__init__( + version=__version__, + base_url=base_url, + max_retries=max_retries, + timeout=timeout, + http_client=http_client, + custom_headers=default_headers, + custom_query=default_query, + _strict_response_validation=_strict_response_validation, + ) + + self.health = health.AsyncHealthResource(self) + self.organizations = organizations.AsyncOrganizationsResource(self) + self.users = users.AsyncUsersResource(self) + self.projects = projects.AsyncProjectsResource(self) + self.with_raw_response = AsyncCodexWithRawResponse(self) + self.with_streaming_response = AsyncCodexWithStreamedResponse(self) + + @property + @override + def qs(self) -> Querystring: + return Querystring(array_format="comma") + + @property + @override + def auth_headers(self) -> dict[str, str]: + if self._http_bearer: + return self._http_bearer + if self._authenticated_api_key: + return self._authenticated_api_key + if self._public_access_key: + return self._public_access_key + return {} + + @property + def _http_bearer(self) -> dict[str, str]: + bearer_token = self.bearer_token + return {"Authorization": f"Bearer {bearer_token}"} + + @property + def _authenticated_api_key(self) -> dict[str, str]: + api_key = self.api_key + return {"X-API-Key": api_key} + + @property + def _public_access_key(self) -> dict[str, str]: + access_key = self.access_key + return {"X-Access-Key": access_key} + + @property + @override + def default_headers(self) -> dict[str, str | Omit]: + return { + **super().default_headers, + "X-Stainless-Async": f"async:{get_async_library()}", + **self._custom_headers, + } + + def copy( + self, + *, + bearer_token: str | None = None, + api_key: str | None = None, + access_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + http_client: httpx.AsyncClient | None = None, + 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, + set_default_query: Mapping[str, object] | None = None, + _extra_kwargs: Mapping[str, Any] = {}, + ) -> Self: + """ + Create a new client instance re-using the same options given to the current client with optional overriding. + """ + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + + http_client = http_client or self._client + return self.__class__( + bearer_token=bearer_token or self.bearer_token, + api_key=api_key or self.api_key, + access_key=access_key or self.access_key, + base_url=base_url or self.base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + **_extra_kwargs, + ) + + # Alias for `copy` for nicer inline usage, e.g. + # client.with_options(timeout=10).foo.create(...) + with_options = copy + + @override + def _make_status_error( + self, + err_msg: str, + *, + body: object, + response: httpx.Response, + ) -> APIStatusError: + if response.status_code == 400: + return _exceptions.BadRequestError(err_msg, response=response, body=body) + + if response.status_code == 401: + return _exceptions.AuthenticationError(err_msg, response=response, body=body) + + if response.status_code == 403: + return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) + + if response.status_code == 404: + return _exceptions.NotFoundError(err_msg, response=response, body=body) + + if response.status_code == 409: + return _exceptions.ConflictError(err_msg, response=response, body=body) + + if response.status_code == 422: + return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) + + if response.status_code == 429: + return _exceptions.RateLimitError(err_msg, response=response, body=body) + + if response.status_code >= 500: + return _exceptions.InternalServerError(err_msg, response=response, body=body) + return APIStatusError(err_msg, response=response, body=body) + + +class CodexWithRawResponse: + def __init__(self, client: Codex) -> None: + self.health = health.HealthResourceWithRawResponse(client.health) + self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations) + self.users = users.UsersResourceWithRawResponse(client.users) + self.projects = projects.ProjectsResourceWithRawResponse(client.projects) + + +class AsyncCodexWithRawResponse: + def __init__(self, client: AsyncCodex) -> None: + self.health = health.AsyncHealthResourceWithRawResponse(client.health) + self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations) + self.users = users.AsyncUsersResourceWithRawResponse(client.users) + self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) + + +class CodexWithStreamedResponse: + def __init__(self, client: Codex) -> None: + self.health = health.HealthResourceWithStreamingResponse(client.health) + self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations) + self.users = users.UsersResourceWithStreamingResponse(client.users) + self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) + + +class AsyncCodexWithStreamedResponse: + def __init__(self, client: AsyncCodex) -> None: + self.health = health.AsyncHealthResourceWithStreamingResponse(client.health) + self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations) + self.users = users.AsyncUsersResourceWithStreamingResponse(client.users) + self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) + + +Client = Codex + +AsyncClient = AsyncCodex diff --git a/src/codex/_compat.py b/src/codex/_compat.py new file mode 100644 index 00000000..92d9ee61 --- /dev/null +++ b/src/codex/_compat.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload +from datetime import date, datetime +from typing_extensions import Self, Literal + +import pydantic +from pydantic.fields import FieldInfo + +from ._types import IncEx, StrBytesIntFloat + +_T = TypeVar("_T") +_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) + +# --------------- Pydantic v2 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.") + +# v1 re-exports +if TYPE_CHECKING: + + def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 + ... + + def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: # noqa: ARG001 + ... + + def get_args(t: type[Any]) -> tuple[Any, ...]: # noqa: ARG001 + ... + + def is_union(tp: type[Any] | None) -> bool: # noqa: ARG001 + ... + + def get_origin(t: type[Any]) -> type[Any] | None: # noqa: ARG001 + ... + + def is_literal_type(type_: type[Any]) -> bool: # noqa: ARG001 + ... + + def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 + ... + +else: + if PYDANTIC_V2: + from pydantic.v1.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 + else: + 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.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: + # TODO: provide an error message here? + ConfigDict = None + + +# renamed methods / properties +def parse_obj(model: type[_ModelT], value: object) -> _ModelT: + if PYDANTIC_V2: + return model.model_validate(value) + else: + return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + + +def field_is_required(field: FieldInfo) -> bool: + if PYDANTIC_V2: + return field.is_required() + return field.required # type: ignore + + +def field_get_default(field: FieldInfo) -> Any: + value = field.get_default() + if PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value + + +def field_outer_type(field: FieldInfo) -> Any: + if PYDANTIC_V2: + return field.annotation + return field.outer_type_ # type: ignore + + +def get_model_config(model: type[pydantic.BaseModel]) -> Any: + if PYDANTIC_V2: + return model.model_config + return model.__config__ # type: ignore + + +def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: + if PYDANTIC_V2: + return model.model_fields + return model.__fields__ # type: ignore + + +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 + + +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 + + +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", +) -> dict[str, Any]: + if PYDANTIC_V2 or hasattr(model, "model_dump"): + return model.model_dump( + mode=mode, + exclude=exclude, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + # warnings are not supported in Pydantic v1 + warnings=warnings if PYDANTIC_V2 else True, + ) + return cast( + "dict[str, Any]", + model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + exclude=exclude, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + ), + ) + + +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] + + +# generic models +if TYPE_CHECKING: + + class GenericModel(pydantic.BaseModel): ... + +else: + if PYDANTIC_V2: + # there no longer needs to be a distinction in v2 but + # we still have to create our own subclass to avoid + # inconsistent MRO ordering errors + class GenericModel(pydantic.BaseModel): ... + + else: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + + +# cached properties +if TYPE_CHECKING: + cached_property = property + + # we define a separate type (copied from typeshed) + # that represents that `cached_property` is `set`able + # at runtime, which differs from `@property`. + # + # this is a separate type as editors likely special case + # `@property` and we don't want to cause issues just to have + # more helpful internal types. + + class typed_cached_property(Generic[_T]): + func: Callable[[Any], _T] + attrname: str | None + + def __init__(self, func: Callable[[Any], _T]) -> None: ... + + @overload + 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 | Self: + raise NotImplementedError() + + 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: ... +else: + from functools import cached_property as cached_property + + typed_cached_property = cached_property diff --git a/src/codex/_constants.py b/src/codex/_constants.py new file mode 100644 index 00000000..a2ac3b6f --- /dev/null +++ b/src/codex/_constants.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import httpx + +RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response" +OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" + +# default timeout is 1 minute +DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0) +DEFAULT_MAX_RETRIES = 2 +DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) + +INITIAL_RETRY_DELAY = 0.5 +MAX_RETRY_DELAY = 8.0 diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py new file mode 100644 index 00000000..90164ee4 --- /dev/null +++ b/src/codex/_exceptions.py @@ -0,0 +1,108 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +__all__ = [ + "BadRequestError", + "AuthenticationError", + "PermissionDeniedError", + "NotFoundError", + "ConflictError", + "UnprocessableEntityError", + "RateLimitError", + "InternalServerError", +] + + +class CodexError(Exception): + pass + + +class APIError(CodexError): + message: str + request: httpx.Request + + body: object | None + """The API response body. + + If the API responded with a valid JSON structure then this property will be the + decoded result. + + If it isn't a valid JSON structure then this will be the raw response. + + If there was no response associated with this error then it will be `None`. + """ + + def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: # noqa: ARG002 + super().__init__(message) + self.request = request + self.message = message + self.body = body + + +class APIResponseValidationError(APIError): + response: httpx.Response + status_code: int + + def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None: + super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body) + self.response = response + self.status_code = response.status_code + + +class APIStatusError(APIError): + """Raised when an API response has a status code of 4xx or 5xx.""" + + response: httpx.Response + status_code: int + + def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None: + super().__init__(message, response.request, body=body) + self.response = response + self.status_code = response.status_code + + +class APIConnectionError(APIError): + def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None: + super().__init__(message, request, body=None) + + +class APITimeoutError(APIConnectionError): + def __init__(self, request: httpx.Request) -> None: + super().__init__(message="Request timed out.", request=request) + + +class BadRequestError(APIStatusError): + status_code: Literal[400] = 400 # pyright: ignore[reportIncompatibleVariableOverride] + + +class AuthenticationError(APIStatusError): + status_code: Literal[401] = 401 # pyright: ignore[reportIncompatibleVariableOverride] + + +class PermissionDeniedError(APIStatusError): + status_code: Literal[403] = 403 # pyright: ignore[reportIncompatibleVariableOverride] + + +class NotFoundError(APIStatusError): + status_code: Literal[404] = 404 # pyright: ignore[reportIncompatibleVariableOverride] + + +class ConflictError(APIStatusError): + status_code: Literal[409] = 409 # pyright: ignore[reportIncompatibleVariableOverride] + + +class UnprocessableEntityError(APIStatusError): + status_code: Literal[422] = 422 # pyright: ignore[reportIncompatibleVariableOverride] + + +class RateLimitError(APIStatusError): + status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride] + + +class InternalServerError(APIStatusError): + pass diff --git a/src/codex/_files.py b/src/codex/_files.py new file mode 100644 index 00000000..715cc207 --- /dev/null +++ b/src/codex/_files.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import io +import os +import pathlib +from typing import overload +from typing_extensions import TypeGuard + +import anyio + +from ._types import ( + FileTypes, + FileContent, + RequestFiles, + HttpxFileTypes, + Base64FileInput, + HttpxFileContent, + HttpxRequestFiles, +) +from ._utils import is_tuple_t, is_mapping_t, is_sequence_t + + +def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: + return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + + +def is_file_content(obj: object) -> TypeGuard[FileContent]: + return ( + isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + ) + + +def assert_is_file_content(obj: object, *, key: str | None = None) -> None: + if not is_file_content(obj): + prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" + raise RuntimeError( + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + ) from None + + +@overload +def to_httpx_files(files: None) -> None: ... + + +@overload +def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... + + +def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: + if files is None: + return None + + if is_mapping_t(files): + files = {key: _transform_file(file) for key, file in files.items()} + elif is_sequence_t(files): + files = [(key, _transform_file(file)) for key, file in files] + else: + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") + + return files + + +def _transform_file(file: FileTypes) -> HttpxFileTypes: + if is_file_content(file): + if isinstance(file, os.PathLike): + path = pathlib.Path(file) + return (path.name, path.read_bytes()) + + return file + + if is_tuple_t(file): + 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: + if isinstance(file, os.PathLike): + return pathlib.Path(file).read_bytes() + return file + + +@overload +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 | None) -> HttpxRequestFiles | None: + if files is None: + return None + + if is_mapping_t(files): + files = {key: await _async_transform_file(file) for key, file in files.items()} + elif is_sequence_t(files): + files = [(key, await _async_transform_file(file)) for key, file in files] + else: + raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence") + + return files + + +async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: + if is_file_content(file): + if isinstance(file, os.PathLike): + path = anyio.Path(file) + return (path.name, await path.read_bytes()) + + return file + + if is_tuple_t(file): + 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: + if isinstance(file, os.PathLike): + return await anyio.Path(file).read_bytes() + + return file diff --git a/src/codex/_models.py b/src/codex/_models.py new file mode 100644 index 00000000..9a918aab --- /dev/null +++ b/src/codex/_models.py @@ -0,0 +1,795 @@ +from __future__ import annotations + +import os +import inspect +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +from datetime import date, datetime +from typing_extensions import ( + Unpack, + Literal, + ClassVar, + Protocol, + Required, + ParamSpec, + TypedDict, + TypeGuard, + final, + override, + runtime_checkable, +) + +import pydantic +import pydantic.generics +from pydantic.fields import FieldInfo + +from ._types import ( + Body, + IncEx, + Query, + ModelT, + Headers, + Timeout, + NotGiven, + AnyMapping, + HttpxRequestFiles, +) +from ._utils import ( + PropertyInfo, + is_list, + is_given, + json_safe, + lru_cache, + is_mapping, + parse_date, + coerce_boolean, + parse_datetime, + strip_not_given, + extract_type_arg, + is_annotated_type, + is_type_alias_type, + strip_annotated_type, +) +from ._compat import ( + PYDANTIC_V2, + ConfigDict, + GenericModel as BaseGenericModel, + get_args, + is_union, + parse_obj, + get_origin, + is_literal_type, + get_model_config, + get_model_fields, + field_get_default, +) +from ._constants import RAW_RESPONSE_HEADER + +if TYPE_CHECKING: + from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema + +__all__ = ["BaseModel", "GenericModel"] + +_T = TypeVar("_T") +_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel") + +P = ParamSpec("P") + + +@runtime_checkable +class _ConfigProtocol(Protocol): + allow_population_by_field_name: bool + + +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: + + @property + @override + def model_fields_set(self) -> set[str]: + # a forwards-compat shim for pydantic v2 + return self.__fields_set__ # type: ignore + + class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] + extra: Any = pydantic.Extra.allow # type: ignore + + def to_dict( + self, + *, + mode: Literal["json", "python"] = "python", + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> dict[str, object]: + """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + mode: + If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`. + If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)` + + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + 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 from the output. + exclude_none: Whether to exclude fields that have a value of `None` from the output. + warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2. + """ + return self.model_dump( + mode=mode, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) + + def to_json( + self, + *, + indent: int | None = 2, + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> str: + """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation). + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2` + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that have the default value. + exclude_none: Whether to exclude fields that have a value of `None`. + warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2. + """ + return self.model_dump_json( + indent=indent, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) + + @override + def __str__(self) -> str: + # mypy complains about an invalid self arg + 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( # pyright: ignore[reportIncompatibleMethodOverride] + __cls: Type[ModelT], + _fields_set: set[str] | None = None, + **values: object, + ) -> ModelT: + m = __cls.__new__(__cls) + fields_values: dict[str, object] = {} + + config = get_model_config(__cls) + populate_by_name = ( + config.allow_population_by_field_name + if isinstance(config, _ConfigProtocol) + else config.get("populate_by_name") + ) + + if _fields_set is None: + _fields_set = set() + + 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): + key = name + + if key in values: + fields_values[name] = _construct_field(value=values[key], field=field, key=key) + _fields_set.add(name) + else: + fields_values[name] = field_get_default(field) + + _extra = {} + for key, value in values.items(): + if key not in model_fields: + if PYDANTIC_V2: + _extra[key] = value + else: + _fields_set.add(key) + fields_values[key] = value + + 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: + # 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) + + return m + + if not TYPE_CHECKING: + # type checkers incorrectly complain about this assignment + # because the type signatures are technically different + # although not in practice + model_construct = construct + + if not PYDANTIC_V2: + # 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 + # pydantic version they are currently using + + @override + def model_dump( + self, + *, + mode: Literal["json", "python"] | str = "python", + include: IncEx | None = None, + exclude: IncEx | None = None, + by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + context: dict[str, Any] | None = None, + serialize_as_any: bool = False, + ) -> dict[str, Any]: + """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump + + Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + 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. + 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. + + Returns: + A dictionary representation of the model. + """ + 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: + raise ValueError("warnings is only supported in Pydantic v2") + if context is not None: + 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") + dumped = super().dict( # pyright: ignore[reportDeprecated] + include=include, + exclude=exclude, + by_alias=by_alias, + 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 = None, + exclude: IncEx | None = None, + by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + context: dict[str, Any] | None = None, + serialize_as_any: bool = False, + ) -> str: + """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json + + Generates a JSON representation of the model using Pydantic's `to_json` method. + + Args: + indent: Indentation to use in the JSON output. If None is passed, the output will be compact. + include: Field(s) to include in the JSON output. Can take either a string or set of strings. + exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings. + by_alias: Whether to serialize using field aliases. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that have the default value. + exclude_none: Whether to exclude fields that have a value of `None`. + round_trip: Whether to use serialization/deserialization between JSON and class instance. + warnings: Whether to show any warnings that occurred during serialization. + + Returns: + A JSON string representation of the model. + """ + if round_trip != False: + raise ValueError("round_trip is only supported in Pydantic v2") + if warnings != True: + raise ValueError("warnings is only supported in Pydantic v2") + if context is not None: + 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().json( # type: ignore[reportDeprecated] + indent=indent, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + +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: + type_ = cast(type, field.outer_type_) # type: ignore + + if type_ is None: + raise RuntimeError(f"Unexpected field type is None for {key}") + + return construct_type(value=value, type_=type_) + + +def is_basemodel(type_: type) -> bool: + """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" + if is_union(type_): + for variant in get_args(type_): + if is_basemodel(variant): + return True + + return False + + return is_basemodel_type(type_) + + +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) + + +def build( + base_model_cls: Callable[P, _BaseModelT], + *args: P.args, + **kwargs: P.kwargs, +) -> _BaseModelT: + """Construct a BaseModel class without validation. + + This is useful for cases where you need to instantiate a `BaseModel` + from an API response as this provides type-safe params which isn't supported + by helpers like `construct_type()`. + + ```py + build(MyModel, my_field_a="foo", my_field_b=123) + ``` + """ + if args: + raise TypeError( + "Received positional arguments which are not supported; Keyword arguments must be used instead", + ) + + return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs)) + + +def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: + """Loose coercion to the expected type with construction of nested values. + + Note: the returned value from this function is not guaranteed to match the + given type. + """ + return cast(_T, construct_type(value=value, type_=type_)) + + +def construct_type(*, value: object, type_: object) -> 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. + """ + # 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_): + type_ = type_.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if is_annotated_type(type_): + meta: tuple[Any, ...] = get_args(type_)[1:] + type_ = extract_type_arg(type_, 0) + else: + meta = tuple() + + # we need to use the origin class for any types that are subscripted generics + # e.g. Dict[str, object] + origin = get_origin(type_) or type_ + args = get_args(type_) + + if is_union(origin): + try: + return validate_type(type_=cast("type[object]", type_), value=value) + except Exception: + pass + + # if the type is a discriminated union then we want to construct the right variant + # in the union, even if the data doesn't match exactly, otherwise we'd break code + # that relies on the constructed class types, e.g. + # + # class FooType: + # kind: Literal['foo'] + # value: str + # + # class BarType: + # kind: Literal['bar'] + # value: int + # + # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then + # we'd end up constructing `FooType` when it should be `BarType`. + discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta) + if discriminator and is_mapping(value): + variant_value = value.get(discriminator.field_alias_from or discriminator.field_name) + if variant_value and isinstance(variant_value, str): + variant_type = discriminator.mapping.get(variant_value) + if variant_type: + return construct_type(type_=variant_type, value=value) + + # if the data is not valid, use the first variant that doesn't fail while deserializing + for variant in args: + try: + return construct_type(value=value, type_=variant) + except Exception: + continue + + raise RuntimeError(f"Could not convert data into a valid instance of {type_}") + + if origin == dict: + if not is_mapping(value): + return value + + _, 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 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] + + if is_mapping(value): + if issubclass(type_, BaseModel): + return type_.construct(**value) # type: ignore[arg-type] + + return cast(Any, type_).construct(**value) + + if origin == list: + if not is_list(value): + return value + + inner_type = args[0] # List[inner_type] + return [construct_type(value=entry, type_=inner_type) for entry in value] + + if origin == float: + if isinstance(value, int): + coerced = float(value) + if coerced != value: + return value + return coerced + + return value + + if type_ == datetime: + try: + return parse_datetime(value) # type: ignore + except Exception: + return value + + if type_ == date: + try: + return parse_date(value) # type: ignore + except Exception: + return value + + return value + + +@runtime_checkable +class CachedDiscriminatorType(Protocol): + __discriminator__: DiscriminatorDetails + + +class DiscriminatorDetails: + field_name: str + """The name of the discriminator field in the variant class, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] + ``` + + Will result in field_name='type' + """ + + field_alias_from: str | None + """The name of the discriminator field in the API response, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] = Field(alias='type_from_api') + ``` + + Will result in field_alias_from='type_from_api' + """ + + mapping: dict[str, type] + """Mapping of discriminator value to variant type, e.g. + + {'foo': FooVariant, 'bar': BarVariant} + """ + + def __init__( + self, + *, + mapping: dict[str, type], + discriminator_field: str, + discriminator_alias: str | None, + ) -> None: + self.mapping = mapping + self.field_name = discriminator_field + self.field_alias_from = discriminator_alias + + +def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: + if isinstance(union, CachedDiscriminatorType): + return union.__discriminator__ + + discriminator_field_name: str | None = None + + for annotation in meta_annotations: + if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None: + discriminator_field_name = annotation.discriminator + break + + if not discriminator_field_name: + return None + + mapping: dict[str, type] = {} + discriminator_alias: str | None = None + + 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: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field.get("serialization_alias") + + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: + 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: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field_info.alias + + if field_info.annotation and is_literal_type(field_info.annotation): + for entry in get_args(field_info.annotation): + if isinstance(entry, str): + mapping[entry] = variant + + if not mapping: + return None + + details = DiscriminatorDetails( + mapping=mapping, + discriminator_field=discriminator_field_name, + discriminator_alias=discriminator_alias, + ) + cast(CachedDiscriminatorType, union).__discriminator__ = details + return details + + +def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: + schema = model.__pydantic_core_schema__ + if schema["type"] != "model": + return None + + 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 + + return cast("ModelField", field) # pyright: ignore[reportUnnecessaryCast] + + +def validate_type(*, type_: type[_T], value: object) -> _T: + """Strict validation that the given value matches the expected type""" + if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): + return cast(_T, parse_obj(type_, value)) + + return cast(_T, _validate_non_model_type(type_=type_, value=value)) + + +def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: + """Add a pydantic config for the given type. + + Note: this is a no-op on Pydantic v1. + """ + setattr(typ, "__pydantic_config__", config) # noqa: B010 + + +# our use of subclasssing here causes weirdness for type checkers, +# so we just pretend that we don't subclass +if TYPE_CHECKING: + GenericModel = BaseModel +else: + + class GenericModel(BaseGenericModel, BaseModel): + pass + + +if PYDANTIC_V2: + from pydantic import TypeAdapter as _TypeAdapter + + _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) + + if TYPE_CHECKING: + from pydantic import TypeAdapter + else: + TypeAdapter = _CachedTypeAdapter + + def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: + return TypeAdapter(type_).validate_python(value) + +elif not TYPE_CHECKING: # TODO: condition is weird + + class RootModel(GenericModel, Generic[_T]): + """Used as a placeholder to easily convert runtime types to a Pydantic format + to provide validation. + + For example: + ```py + validated = RootModel[int](__root__="5").__root__ + # validated: 5 + ``` + """ + + __root__: _T + + def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: + model = _create_pydantic_model(type_).validate(value) + return cast(_T, model.__root__) + + def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]: + return RootModel[type_] # type: ignore + + +class FinalRequestOptionsInput(TypedDict, total=False): + method: Required[str] + url: Required[str] + params: Query + headers: Headers + max_retries: int + timeout: float | Timeout | None + files: HttpxRequestFiles | None + idempotency_key: str + json_data: Body + extra_json: AnyMapping + + +@final +class FinalRequestOptions(pydantic.BaseModel): + method: str + url: str + params: Query = {} + headers: Union[Headers, NotGiven] = NotGiven() + max_retries: Union[int, NotGiven] = NotGiven() + timeout: Union[float, Timeout, None, NotGiven] = NotGiven() + files: Union[HttpxRequestFiles, None] = None + idempotency_key: Union[str, None] = None + post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + + # 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: + + class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] + arbitrary_types_allowed: bool = True + + def get_max_retries(self, max_retries: int) -> int: + if isinstance(self.max_retries, NotGiven): + return max_retries + return self.max_retries + + def _strip_raw_response_header(self) -> None: + if not is_given(self.headers): + return + + if self.headers.get(RAW_RESPONSE_HEADER): + self.headers = {**self.headers} + self.headers.pop(RAW_RESPONSE_HEADER) + + # override the `construct` method so that we can run custom transformations. + # this is necessary as we don't want to do any actual runtime type checking + # (which means we can't use validators) but we do want to ensure that `NotGiven` + # values are not present + # + # type ignore required because we're adding explicit types to `**values` + @classmethod + def construct( # type: ignore + cls, + _fields_set: set[str] | None = None, + **values: Unpack[FinalRequestOptionsInput], + ) -> FinalRequestOptions: + kwargs: dict[str, Any] = { + # we unconditionally call `strip_not_given` on any value + # as it will just ignore any non-mapping types + 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 not TYPE_CHECKING: + # type checkers incorrectly complain about this assignment + model_construct = construct diff --git a/src/codex/_qs.py b/src/codex/_qs.py new file mode 100644 index 00000000..274320ca --- /dev/null +++ b/src/codex/_qs.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +from typing import Any, List, Tuple, Union, Mapping, TypeVar +from urllib.parse import parse_qs, urlencode +from typing_extensions import Literal, get_args + +from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._utils import flatten + +_T = TypeVar("_T") + + +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + +PrimitiveData = Union[str, int, float, bool, None] +# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] +# https://github.com/microsoft/pyright/issues/3555 +Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"] +Params = Mapping[str, Data] + + +class Querystring: + array_format: ArrayFormat + nested_format: NestedFormat + + def __init__( + self, + *, + array_format: ArrayFormat = "repeat", + nested_format: NestedFormat = "brackets", + ) -> None: + self.array_format = array_format + self.nested_format = nested_format + + def parse(self, query: str) -> Mapping[str, object]: + # Note: custom format syntax is not supported yet + return parse_qs(query) + + def stringify( + self, + params: Params, + *, + array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, + nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + ) -> str: + return urlencode( + self.stringify_items( + params, + array_format=array_format, + nested_format=nested_format, + ) + ) + + def stringify_items( + self, + params: Params, + *, + array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, + nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + ) -> list[tuple[str, str]]: + opts = Options( + qs=self, + array_format=array_format, + nested_format=nested_format, + ) + return flatten([self._stringify_item(key, value, opts) for key, value in params.items()]) + + def _stringify_item( + self, + key: str, + value: Data, + opts: Options, + ) -> list[tuple[str, str]]: + if isinstance(value, Mapping): + items: list[tuple[str, str]] = [] + nested_format = opts.nested_format + for subkey, subvalue in value.items(): + items.extend( + self._stringify_item( + # TODO: error if unknown format + f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]", + subvalue, + opts, + ) + ) + return items + + if isinstance(value, (list, tuple)): + array_format = opts.array_format + if array_format == "comma": + return [ + ( + key, + ",".join(self._primitive_value_to_str(item) for item in value if item is not None), + ), + ] + elif array_format == "repeat": + items = [] + for item in value: + items.extend(self._stringify_item(key, item, opts)) + return items + elif array_format == "indices": + raise NotImplementedError("The array indices format is not supported yet") + elif array_format == "brackets": + items = [] + key = key + "[]" + for item in value: + items.extend(self._stringify_item(key, item, opts)) + return items + else: + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + serialised = self._primitive_value_to_str(value) + if not serialised: + return [] + return [(key, serialised)] + + def _primitive_value_to_str(self, value: PrimitiveData) -> str: + # copied from httpx + if value is True: + return "true" + elif value is False: + return "false" + elif value is None: + return "" + return str(value) + + +_qs = Querystring() +parse = _qs.parse +stringify = _qs.stringify +stringify_items = _qs.stringify_items + + +class Options: + array_format: ArrayFormat + nested_format: NestedFormat + + def __init__( + self, + qs: Querystring = _qs, + *, + array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, + nested_format: NotGivenOr[NestedFormat] = 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/codex/_resource.py b/src/codex/_resource.py new file mode 100644 index 00000000..7ba43eeb --- /dev/null +++ b/src/codex/_resource.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import time +from typing import TYPE_CHECKING + +import anyio + +if TYPE_CHECKING: + from ._client import Codex, AsyncCodex + + +class SyncAPIResource: + _client: Codex + + def __init__(self, client: Codex) -> None: + self._client = client + self._get = client.get + self._post = client.post + self._patch = client.patch + self._put = client.put + self._delete = client.delete + self._get_api_list = client.get_api_list + + def _sleep(self, seconds: float) -> None: + time.sleep(seconds) + + +class AsyncAPIResource: + _client: AsyncCodex + + def __init__(self, client: AsyncCodex) -> None: + self._client = client + self._get = client.get + self._post = client.post + self._patch = client.patch + self._put = client.put + self._delete = client.delete + self._get_api_list = client.get_api_list + + async def _sleep(self, seconds: float) -> None: + await anyio.sleep(seconds) diff --git a/src/codex/_response.py b/src/codex/_response.py new file mode 100644 index 00000000..b63926f4 --- /dev/null +++ b/src/codex/_response.py @@ -0,0 +1,824 @@ +from __future__ import annotations + +import os +import inspect +import logging +import datetime +import functools +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Union, + Generic, + TypeVar, + Callable, + Iterator, + AsyncIterator, + cast, + overload, +) +from typing_extensions import Awaitable, ParamSpec, override, get_origin + +import anyio +import httpx +import pydantic + +from ._types import NoneType +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 +from ._exceptions import CodexError, APIResponseValidationError + +if TYPE_CHECKING: + from ._models import FinalRequestOptions + from ._base_client import BaseClient + + +P = ParamSpec("P") +R = TypeVar("R") +_T = TypeVar("_T") +_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]") +_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]") + +log: logging.Logger = logging.getLogger(__name__) + + +class BaseAPIResponse(Generic[R]): + _cast_to: type[R] + _client: BaseClient[Any, Any] + _parsed_by_type: dict[type[Any], Any] + _is_sse_stream: bool + _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None + _options: FinalRequestOptions + + http_response: httpx.Response + + retries_taken: int + """The number of retries made. If no retries happened this will be `0`""" + + def __init__( + self, + *, + raw: httpx.Response, + cast_to: type[R], + client: BaseClient[Any, Any], + 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 + self._parsed_by_type = {} + self._is_sse_stream = stream + self._stream_cls = stream_cls + self._options = options + self.http_response = raw + self.retries_taken = retries_taken + + @property + def headers(self) -> httpx.Headers: + return self.http_response.headers + + @property + def http_request(self) -> httpx.Request: + """Returns the httpx Request instance associated with the current response.""" + return self.http_response.request + + @property + def status_code(self) -> int: + return self.http_response.status_code + + @property + def url(self) -> httpx.URL: + """Returns the URL for which the request was made.""" + return self.http_response.url + + @property + def method(self) -> str: + return self.http_request.method + + @property + def http_version(self) -> str: + return self.http_response.http_version + + @property + def elapsed(self) -> datetime.timedelta: + """The time taken for the complete request/response cycle to complete.""" + return self.http_response.elapsed + + @property + def is_closed(self) -> bool: + """Whether or not the response body has been closed. + + If this is False then there is response data that has not been read yet. + You must either fully consume the response body or call `.close()` + before discarding the response to prevent resource leaks. + """ + return self.http_response.is_closed + + @override + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>" + ) + + 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 cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + if self._is_sse_stream: + if to: + if not is_stream_class_type(to): + raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}") + + return cast( + _T, + to( + cast_to=extract_stream_chunk_type( + to, + failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]", + ), + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + if self._stream_cls: + return cast( + R, + self._stream_cls( + cast_to=extract_stream_chunk_type(self._stream_cls), + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls) + if stream_cls is None: + raise MissingStreamClassError() + + return cast( + R, + stream_cls( + cast_to=cast_to, + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + if cast_to is NoneType: + return cast(R, None) + + response = self.http_response + if cast_to == str: + return cast(R, response.text) + + if cast_to == bytes: + return cast(R, response.content) + + if cast_to == int: + return cast(R, int(response.text)) + + if cast_to == float: + return cast(R, float(response.text)) + + if cast_to == bool: + return cast(R, response.text.lower() == "true") + + origin = get_origin(cast_to) or cast_to + + if origin == APIResponse: + raise RuntimeError("Unexpected state - cast_to is `APIResponse`") + + if inspect.isclass(origin) and issubclass(origin, httpx.Response): + # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response + # and pass that class to our request functions. We cannot change the variance to be either + # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct + # the response class ourselves but that is something that should be supported directly in httpx + # as it would be easy to incorrectly construct the Response object due to the multitude of arguments. + if cast_to != httpx.Response: + 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): + raise TypeError("Pydantic models must subclass our base model type, e.g. `from codex import BaseModel`") + + if ( + cast_to is not object + and not origin is list + and not origin is dict + and not origin is Union + and not issubclass(origin, BaseModel) + ): + raise RuntimeError( + f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." + ) + + # 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 is_basemodel(cast_to): + try: + data = response.json() + except Exception as exc: + log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc) + else: + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + if self._client._strict_response_validation: + raise APIResponseValidationError( + response=response, + message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.", + body=response.text, + ) + + # If the API responds with content that isn't JSON then we just return + # the (decoded) text without performing any parsing so that you can still + # handle the response however you need to. + return response.text # type: ignore + + data = response.json() + + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + +class APIResponse(BaseAPIResponse[R]): + @overload + def parse(self, *, to: type[_T]) -> _T: ... + + @overload + def parse(self) -> R: ... + + def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from codex import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `int` + - `float` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return self.http_response.read() + except httpx.StreamConsumed as exc: + # The default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message. + raise StreamAlreadyConsumed() from exc + + def text(self) -> str: + """Read and decode the response content into a string.""" + self.read() + return self.http_response.text + + def json(self) -> object: + """Read and decode the JSON response content.""" + self.read() + return self.http_response.json() + + def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.http_response.close() + + def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + for chunk in self.http_response.iter_bytes(chunk_size): + yield chunk + + def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + for chunk in self.http_response.iter_text(chunk_size): + yield chunk + + def iter_lines(self) -> Iterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + for chunk in self.http_response.iter_lines(): + yield chunk + + +class AsyncAPIResponse(BaseAPIResponse[R]): + @overload + async def parse(self, *, to: type[_T]) -> _T: ... + + @overload + 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. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from codex import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + await self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + async def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return await self.http_response.aread() + except httpx.StreamConsumed as exc: + # the default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message + raise StreamAlreadyConsumed() from exc + + async def text(self) -> str: + """Read and decode the response content into a string.""" + await self.read() + return self.http_response.text + + async def json(self) -> object: + """Read and decode the JSON response content.""" + await self.read() + return self.http_response.json() + + async def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.http_response.aclose() + + async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + async for chunk in self.http_response.aiter_bytes(chunk_size): + yield chunk + + async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + async for chunk in self.http_response.aiter_text(chunk_size): + yield chunk + + async def iter_lines(self) -> AsyncIterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + async for chunk in self.http_response.aiter_lines(): + yield chunk + + +class BinaryAPIResponse(APIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(): + f.write(data) + + +class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + async def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(): + await f.write(data) + + +class StreamedBinaryAPIResponse(APIResponse[bytes]): + def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(chunk_size): + f.write(data) + + +class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]): + async def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(chunk_size): + await f.write(data) + + +class MissingStreamClassError(TypeError): + def __init__(self) -> None: + super().__init__( + "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `codex._streaming` for reference", + ) + + +class StreamAlreadyConsumed(CodexError): + """ + Attempted to read or stream content, but the content has already + been streamed. + + This can happen if you use a method like `.iter_lines()` and then attempt + to read th entire response body afterwards, e.g. + + ```py + response = await client.post(...) + async for line in response.iter_lines(): + ... # do something with `line` + + content = await response.read() + # ^ error + ``` + + If you want this behaviour you'll need to either manually accumulate the response + content or call `await response.read()` before iterating over the stream. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream some content, but the content has " + "already been streamed. " + "This could be due to attempting to stream the response " + "content more than once." + "\n\n" + "You can fix this by manually accumulating the response content while streaming " + "or by calling `.read()` before starting to stream." + ) + super().__init__(message) + + +class ResponseContextManager(Generic[_APIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, request_func: Callable[[], _APIResponseT]) -> None: + self._request_func = request_func + self.__response: _APIResponseT | None = None + + def __enter__(self) -> _APIResponseT: + self.__response = self._request_func() + return self.__response + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + self.__response.close() + + +class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None: + self._api_request = api_request + self.__response: _AsyncAPIResponseT | None = None + + async def __aenter__(self) -> _AsyncAPIResponseT: + self.__response = await self._api_request + return self.__response + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + await self.__response.close() + + +def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request)) + + return wrapped + + +def async_to_streamed_response_wrapper( + func: Callable[P, Awaitable[R]], +) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request)) + + return wrapped + + +def to_custom_streamed_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, ResponseContextManager[_APIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request)) + + return wrapped + + +def async_to_custom_streamed_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request)) + + return wrapped + + +def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + + kwargs["extra_headers"] = extra_headers + + return cast(APIResponse[R], func(*args, **kwargs)) + + return wrapped + + +def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + + kwargs["extra_headers"] = extra_headers + + return cast(AsyncAPIResponse[R], await func(*args, **kwargs)) + + return wrapped + + +def to_custom_raw_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, _APIResponseT]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(_APIResponseT, func(*args, **kwargs)) + + return wrapped + + +def async_to_custom_raw_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, Awaitable[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs)) + + return wrapped + + +def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type: + """Given a type like `APIResponse[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(APIResponse[bytes]): + ... + + extract_response_type(MyResponse) -> bytes + ``` + """ + return extract_type_var_from_base( + typ, + generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)), + index=0, + ) diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py new file mode 100644 index 00000000..3af102ce --- /dev/null +++ b/src/codex/_streaming.py @@ -0,0 +1,333 @@ +# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py +from __future__ import annotations + +import json +import inspect +from types import TracebackType +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable + +import httpx + +from ._utils import extract_type_var_from_base + +if TYPE_CHECKING: + from ._client import Codex, AsyncCodex + + +_T = TypeVar("_T") + + +class Stream(Generic[_T]): + """Provides the core interface to iterate over a synchronous stream response.""" + + response: httpx.Response + + _decoder: SSEBytesDecoder + + def __init__( + self, + *, + cast_to: type[_T], + response: httpx.Response, + client: Codex, + ) -> None: + self.response = response + self._cast_to = cast_to + self._client = client + self._decoder = client._make_sse_decoder() + self._iterator = self.__stream__() + + def __next__(self) -> _T: + return self._iterator.__next__() + + def __iter__(self) -> Iterator[_T]: + for item in self._iterator: + yield item + + def _iter_events(self) -> Iterator[ServerSentEvent]: + yield from self._decoder.iter_bytes(self.response.iter_bytes()) + + def __stream__(self) -> Iterator[_T]: + cast_to = cast(Any, self._cast_to) + response = self.response + 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: + ... + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.response.close() + + +class AsyncStream(Generic[_T]): + """Provides the core interface to iterate over an asynchronous stream response.""" + + response: httpx.Response + + _decoder: SSEDecoder | SSEBytesDecoder + + def __init__( + self, + *, + cast_to: type[_T], + response: httpx.Response, + client: AsyncCodex, + ) -> None: + self.response = response + self._cast_to = cast_to + self._client = client + self._decoder = client._make_sse_decoder() + self._iterator = self.__stream__() + + async def __anext__(self) -> _T: + return await self._iterator.__anext__() + + async def __aiter__(self) -> AsyncIterator[_T]: + async for item in self._iterator: + yield item + + async def _iter_events(self) -> AsyncIterator[ServerSentEvent]: + async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()): + yield sse + + async def __stream__(self) -> AsyncIterator[_T]: + cast_to = cast(Any, self._cast_to) + response = self.response + 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: + ... + + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.response.aclose() + + +class ServerSentEvent: + def __init__( + self, + *, + event: str | None = None, + data: str | None = None, + id: str | None = None, + retry: int | None = None, + ) -> None: + if data is None: + data = "" + + self._id = id + self._data = data + self._event = event or None + self._retry = retry + + @property + def event(self) -> str | None: + return self._event + + @property + def id(self) -> str | None: + return self._id + + @property + def retry(self) -> int | None: + return self._retry + + @property + def data(self) -> str: + return self._data + + def json(self) -> Any: + return json.loads(self.data) + + @override + def __repr__(self) -> str: + return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})" + + +class SSEDecoder: + _data: list[str] + _event: str | None + _retry: int | None + _last_event_id: str | None + + def __init__(self) -> None: + self._event = None + self._data = [] + self._last_event_id = None + self._retry = None + + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + for chunk in self._iter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + async for chunk in self._aiter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + async for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + def decode(self, line: str) -> ServerSentEvent | None: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if not self._event and not self._data and not self._last_event_id and self._retry is None: + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = None + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None + + +@runtime_checkable +class SSEBytesDecoder(Protocol): + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + +def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]: + """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`""" + origin = get_origin(typ) or typ + return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream)) + + +def extract_stream_chunk_type( + stream_cls: type, + *, + failure_message: str | None = None, +) -> type: + """Given a type like `Stream[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyStream(Stream[bytes]): + ... + + extract_stream_chunk_type(MyStream) -> bytes + ``` + """ + from ._base_client import Stream, AsyncStream + + return extract_type_var_from_base( + stream_cls, + index=0, + generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), + failure_message=failure_message, + ) diff --git a/src/codex/_types.py b/src/codex/_types.py new file mode 100644 index 00000000..dfa51c2f --- /dev/null +++ b/src/codex/_types.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +from os import PathLike +from typing import ( + IO, + TYPE_CHECKING, + Any, + Dict, + List, + Type, + Tuple, + Union, + Mapping, + TypeVar, + Callable, + Optional, + Sequence, +) +from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable + +import httpx +import pydantic +from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport + +if TYPE_CHECKING: + from ._models import BaseModel + from ._response import APIResponse, AsyncAPIResponse + +Transport = BaseTransport +AsyncTransport = AsyncBaseTransport +Query = Mapping[str, object] +Body = object +AnyMapping = Mapping[str, object] +ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) +_T = TypeVar("_T") + + +# Approximates httpx internal ProxiesTypes and RequestFiles types +# while adding support for `PathLike` instances +ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] +ProxiesTypes = Union[str, Proxy, ProxiesDict] +if TYPE_CHECKING: + Base64FileInput = Union[IO[bytes], PathLike[str]] + FileContent = Union[IO[bytes], bytes, PathLike[str]] +else: + Base64FileInput = Union[IO[bytes], PathLike] + FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. +FileTypes = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], +] +RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] + +# duplicate of the above but without our custom file support +HttpxFileContent = Union[IO[bytes], bytes] +HttpxFileTypes = Union[ + # file (or bytes) + HttpxFileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], HttpxFileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], HttpxFileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]], +] +HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]] + +# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT +# where ResponseT includes `None`. In order to support directly +# passing `None`, overloads would have to be defined for every +# method that uses `ResponseT` which would lead to an unacceptable +# amount of code duplication and make it unreadable. See _base_client.py +# for example usage. +# +# This unfortunately means that you will either have +# to import this type and pass it explicitly: +# +# from codex import NoneType +# client.get('/foo', cast_to=NoneType) +# +# or build it yourself: +# +# client.get('/foo', cast_to=type(None)) +if TYPE_CHECKING: + NoneType: Type[None] +else: + NoneType = type(None) + + +class RequestOptions(TypedDict, total=False): + headers: Headers + max_retries: int + timeout: float | Timeout | None + params: Query + extra_json: AnyMapping + idempotency_key: str + + +# 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 example: + + ```py + def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ... + + + get(timeout=1) # 1s timeout + get(timeout=None) # No timeout + get() # Default timeout behavior, which may not be statically known at the method definition. + ``` + """ + + def __bool__(self) -> Literal[False]: + return False + + @override + def __repr__(self) -> str: + return "NOT_GIVEN" + + +NotGivenOr = Union[_T, NotGiven] +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: + + ```py + # as the default `Content-Type` header is `application/json` that will be sent + client.post("/upload/files", files={"file": b"my raw file content"}) + + # you can't explicitly override the header as it has to be dynamically generated + # 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()}) + ``` + """ + + def __bool__(self) -> Literal[False]: + return False + + +@runtime_checkable +class ModelBuilderProtocol(Protocol): + @classmethod + def build( + cls: type[_T], + *, + response: Response, + data: object, + ) -> _T: ... + + +Headers = Mapping[str, Union[str, Omit]] + + +class HeadersLikeProtocol(Protocol): + def get(self, __key: str) -> str | None: ... + + +HeadersLike = Union[Headers, HeadersLikeProtocol] + +ResponseT = TypeVar( + "ResponseT", + bound=Union[ + object, + str, + None, + "BaseModel", + List[Any], + Dict[str, Any], + Response, + ModelBuilderProtocol, + "APIResponse[Any]", + "AsyncAPIResponse[Any]", + ], +) + +StrBytesIntFloat = Union[str, bytes, int, float] + +# Note: copied from Pydantic +# 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] + + +@runtime_checkable +class InheritsGeneric(Protocol): + """Represents a type that has inherited from `Generic` + + The `__orig_bases__` property can be used to determine the resolved + type variable for a given base class. + """ + + __orig_bases__: tuple[_GenericAlias] + + +class _GenericAlias(Protocol): + __origin__: type[object] + + +class HttpxSendArgs(TypedDict, total=False): + auth: httpx.Auth diff --git a/src/codex/_utils/__init__.py b/src/codex/_utils/__init__.py new file mode 100644 index 00000000..d4fda26f --- /dev/null +++ b/src/codex/_utils/__init__.py @@ -0,0 +1,57 @@ +from ._sync import asyncify as asyncify +from ._proxy import LazyProxy as LazyProxy +from ._utils import ( + flatten as flatten, + is_dict as is_dict, + 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, + is_mapping_t as is_mapping_t, + removeprefix as removeprefix, + removesuffix as removesuffix, + extract_files as extract_files, + is_sequence_t as is_sequence_t, + required_args as required_args, + 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, + maybe_coerce_float as maybe_coerce_float, + get_required_header as get_required_header, + maybe_coerce_boolean as maybe_coerce_boolean, + maybe_coerce_integer as maybe_coerce_integer, +) +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_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, +) +from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator +from ._transform import ( + PropertyInfo as PropertyInfo, + transform as transform, + async_transform as async_transform, + maybe_transform as maybe_transform, + async_maybe_transform as async_maybe_transform, +) +from ._reflection import ( + function_has_argument as function_has_argument, + assert_signatures_in_sync as assert_signatures_in_sync, +) diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py new file mode 100644 index 00000000..80932c4f --- /dev/null +++ b/src/codex/_utils/_logs.py @@ -0,0 +1,25 @@ +import os +import logging + +logger: logging.Logger = logging.getLogger("codex") +httpx_logger: logging.Logger = logging.getLogger("httpx") + + +def _basic_config() -> None: + # e.g. [2023-10-05 14:12:26 - codex._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK" + logging.basicConfig( + format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + +def setup_logging() -> None: + env = os.environ.get("CODEX_LOG") + if env == "debug": + _basic_config() + logger.setLevel(logging.DEBUG) + httpx_logger.setLevel(logging.DEBUG) + elif env == "info": + _basic_config() + logger.setLevel(logging.INFO) + httpx_logger.setLevel(logging.INFO) diff --git a/src/codex/_utils/_proxy.py b/src/codex/_utils/_proxy.py new file mode 100644 index 00000000..ffd883e9 --- /dev/null +++ b/src/codex/_utils/_proxy.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Generic, TypeVar, Iterable, cast +from typing_extensions import override + +T = TypeVar("T") + + +class LazyProxy(Generic[T], ABC): + """Implements data methods to pretend that an instance is another instance. + + This includes forwarding attribute access and other methods. + """ + + # Note: we have to special case proxies that themselves return proxies + # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz` + + def __getattr__(self, attr: str) -> object: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied # pyright: ignore + return getattr(proxied, attr) + + @override + def __repr__(self) -> str: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied.__class__.__name__ + return repr(self.__get_proxied__()) + + @override + def __str__(self) -> str: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied.__class__.__name__ + return str(proxied) + + @override + def __dir__(self) -> Iterable[str]: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return [] + return proxied.__dir__() + + @property # type: ignore + @override + def __class__(self) -> type: # pyright: ignore + proxied = self.__get_proxied__() + if issubclass(type(proxied), LazyProxy): + return type(proxied) + return proxied.__class__ + + def __get_proxied__(self) -> T: + return self.__load__() + + def __as_proxied__(self) -> T: + """Helper method that returns the current proxy, typed as the loaded object""" + return cast(T, self) + + @abstractmethod + def __load__(self) -> T: ... diff --git a/src/codex/_utils/_reflection.py b/src/codex/_utils/_reflection.py new file mode 100644 index 00000000..89aa712a --- /dev/null +++ b/src/codex/_utils/_reflection.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import inspect +from typing import Any, Callable + + +def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool: + """Returns whether or not the given function has a specific parameter""" + sig = inspect.signature(func) + return arg_name in sig.parameters + + +def assert_signatures_in_sync( + source_func: Callable[..., Any], + check_func: Callable[..., Any], + *, + exclude_params: set[str] = set(), +) -> None: + """Ensure that the signature of the second function matches the first.""" + + check_sig = inspect.signature(check_func) + source_sig = inspect.signature(source_func) + + errors: list[str] = [] + + for name, source_param in source_sig.parameters.items(): + if name in exclude_params: + continue + + custom_param = check_sig.parameters.get(name) + if not custom_param: + errors.append(f"the `{name}` param is missing") + continue + + 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(custom_param.annotation)}" + ) + continue + + if errors: + raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors)) diff --git a/src/codex/_utils/_streams.py b/src/codex/_utils/_streams.py new file mode 100644 index 00000000..f4a0208f --- /dev/null +++ b/src/codex/_utils/_streams.py @@ -0,0 +1,12 @@ +from typing import Any +from typing_extensions import Iterator, AsyncIterator + + +def consume_sync_iterator(iterator: Iterator[Any]) -> None: + for _ in iterator: + ... + + +async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None: + async for _ in iterator: + ... diff --git a/src/codex/_utils/_sync.py b/src/codex/_utils/_sync.py new file mode 100644 index 00000000..8b3aaf2b --- /dev/null +++ b/src/codex/_utils/_sync.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +import sys +import asyncio +import functools +import contextvars +from typing import Any, TypeVar, Callable, Awaitable +from typing_extensions import ParamSpec + +T_Retval = TypeVar("T_Retval") +T_ParamSpec = ParamSpec("T_ParamSpec") + + +if sys.version_info >= (3, 9): + to_thread = asyncio.to_thread +else: + # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread + # for Python 3.8 support + async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs + ) -> Any: + """Asynchronously run function *func* in a separate thread. + + Any *args and **kwargs supplied for this function are directly passed + to *func*. Also, the current :class:`contextvars.Context` is propagated, + allowing context variables from the main thread to be accessed in the + separate thread. + + Returns a coroutine that can be awaited to get the eventual result of *func*. + """ + loop = asyncio.events.get_running_loop() + ctx = contextvars.copy_context() + func_call = functools.partial(ctx.run, func, *args, **kwargs) + return await loop.run_in_executor(None, func_call) + + +# 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. For python version 3.9 and above, it uses + asyncio.to_thread to run the function in a separate thread. For python version + 3.8, it uses locally defined copy of the asyncio.to_thread function which was + introduced in python 3.9. + + Usage: + + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result + + + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) + ``` + + ## Arguments + + `function`: a blocking regular callable (e.g. a function) + + ## Return + + An async function that takes the same positional and keyword arguments as the + original one, that when called runs the same original function in a thread worker + and returns the result. + """ + + async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: + return await to_thread(function, *args, **kwargs) + + return wrapper diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py new file mode 100644 index 00000000..a6b62cad --- /dev/null +++ b/src/codex/_utils/_transform.py @@ -0,0 +1,392 @@ +from __future__ import annotations + +import io +import base64 +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 + +import anyio +import pydantic + +from ._utils import ( + is_list, + is_mapping, + is_iterable, +) +from .._files import is_base64_file_input +from ._typing import ( + is_list_type, + is_union_type, + extract_type_arg, + is_iterable_type, + is_required_type, + is_annotated_type, + strip_annotated_type, +) +from .._compat import model_dump, is_typeddict + +_T = TypeVar("_T") + + +# TODO: support for drilling globals() and locals() +# TODO: ensure works correctly with forward references in all cases + + +PropertyFormat = Literal["iso8601", "base64", "custom"] + + +class PropertyInfo: + """Metadata class to be used in Annotated types to provide information about a given type. + + For example: + + class MyParams(TypedDict): + account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')] + + This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API. + """ + + alias: str | None + format: PropertyFormat | None + format_template: str | None + discriminator: str | None + + def __init__( + self, + *, + alias: str | None = None, + format: PropertyFormat | None = None, + format_template: str | None = None, + discriminator: str | None = None, + ) -> None: + self.alias = alias + self.format = format + self.format_template = format_template + self.discriminator = discriminator + + @override + def __repr__(self) -> str: + return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')" + + +def maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `transform()` that allows `None` to be passed. + + See `transform()` for more details. + """ + if data is None: + return None + return transform(data, expected_type) + + +# Wrapper over _transform_recursive providing fake types +def transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = _transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +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. + + This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]` + """ + if is_required_type(type_): + # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]` + type_ = get_args(type_)[0] + + if is_annotated_type(type_): + return type_ + + return 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. + """ + annotated_type = _get_annotated_type(type_) + if annotated_type is None: + # no `Annotated` definition for this type, no transformation needed + return key + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.alias is not None: + return annotation.alias + + return key + + +def _transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + if is_typeddict(stripped_type) and is_mapping(data): + return _transform_typeddict(data, stripped_type) + + 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)) + ): + # 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) + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = _transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True, mode="json") + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return _format_data(data, annotation.format, annotation.format_template) + + return data + + +def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = data.read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + + return data + + +def _transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_) + return result + + +async def async_maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `async_transform()` that allows `None` to be passed. + + See `async_transform()` for more details. + """ + if data is None: + return None + return await async_transform(data, expected_type) + + +async def async_transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +async def _async_transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + if is_typeddict(stripped_type) and is_mapping(data): + return await _async_transform_typeddict(data, stripped_type) + + 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)) + ): + # 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) + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True, mode="json") + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return await _async_format_data(data, annotation.format, annotation.format_template) + + return data + + +async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = await anyio.Path(data).read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + + return data + + +async def _async_transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) + return result diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py new file mode 100644 index 00000000..278749b1 --- /dev/null +++ b/src/codex/_utils/_typing.py @@ -0,0 +1,149 @@ +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 ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) + +from .._types import InheritsGeneric +from .._compat import is_union as _is_union + + +def is_annotated_type(typ: type) -> bool: + return get_origin(typ) == Annotated + + +def is_list_type(typ: type) -> bool: + return (get_origin(typ) or typ) == list + + +def is_iterable_type(typ: type) -> bool: + """If the given type is `typing.Iterable[T]`""" + origin = get_origin(typ) or typ + return origin == Iterable or origin == _c_abc.Iterable + + +def is_union_type(typ: type) -> bool: + return _is_union(get_origin(typ)) + + +def is_required_type(typ: type) -> bool: + return get_origin(typ) == Required + + +def is_typevar(typ: type) -> bool: + # type ignore is required because type checkers + # think this expression will always return False + 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, ...]] +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])) + + return typ + + +def extract_type_arg(typ: type, index: int) -> type: + args = get_args(typ) + try: + return cast(type, args[index]) + except IndexError as err: + raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err + + +def extract_type_var_from_base( + typ: type, + *, + generic_bases: tuple[type, ...], + index: int, + failure_message: str | None = None, +) -> type: + """Given a type like `Foo[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(Foo[bytes]): + ... + + extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes + ``` + + And where a generic subclass is given: + ```py + _T = TypeVar('_T') + class MyResponse(Foo[_T]): + ... + + extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes + ``` + """ + cls = cast(object, get_origin(typ) or typ) + if cls in generic_bases: + # we're given the class directly + return extract_type_arg(typ, index) + + # if a subclass is given + # --- + # this is needed as __orig_bases__ is not present in the typeshed stubs + # because it is intended to be for internal use only, however there does + # not seem to be a way to resolve generic TypeVars for inherited subclasses + # without using it. + if isinstance(cls, InheritsGeneric): + target_base_class: Any | None = None + for base in cls.__orig_bases__: + if base.__origin__ in generic_bases: + target_base_class = base + break + + if target_base_class is None: + raise RuntimeError( + "Could not find the generic base class;\n" + "This should never happen;\n" + f"Does {cls} inherit from one of {generic_bases} ?" + ) + + extracted = extract_type_arg(target_base_class, index) + if is_typevar(extracted): + # If the extracted type argument is itself a type variable + # then that means the subclass itself is generic, so we have + # to resolve the type argument from the class itself, not + # the base class. + # + # Note: if there is more than 1 type argument, the subclass could + # change the ordering of the type arguments, this is not currently + # supported. + return extract_type_arg(typ, index) + + return extracted + + raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}") diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py new file mode 100644 index 00000000..e5811bba --- /dev/null +++ b/src/codex/_utils/_utils.py @@ -0,0 +1,414 @@ +from __future__ import annotations + +import os +import re +import inspect +import functools +from typing import ( + Any, + Tuple, + Mapping, + TypeVar, + Callable, + Iterable, + Sequence, + cast, + 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 + +_T = TypeVar("_T") +_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) +_MappingT = TypeVar("_MappingT", bound=Mapping[str, object]) +_SequenceT = TypeVar("_SequenceT", bound=Sequence[object]) +CallableT = TypeVar("CallableT", bound=Callable[..., Any]) + + +def flatten(t: Iterable[Iterable[_T]]) -> list[_T]: + return [item for sublist in t for item in sublist] + + +def extract_files( + # TODO: this needs to take Dict but variance issues..... + # create protocol type ? + query: Mapping[str, object], + *, + paths: Sequence[Sequence[str]], +) -> list[tuple[str, FileTypes]]: + """Recursively extract files from the given dictionary based on specified paths. + + A path may look like this ['foo', 'files', '', 'data']. + + Note: this mutates the given dictionary. + """ + files: list[tuple[str, FileTypes]] = [] + for path in paths: + files.extend(_extract_items(query, path, index=0, flattened_key=None)) + return files + + +def _extract_items( + obj: object, + path: Sequence[str], + *, + index: int, + flattened_key: str | None, +) -> list[tuple[str, FileTypes]]: + try: + key = path[index] + except IndexError: + if isinstance(obj, NotGiven): + # no value was provided - we can safely ignore + return [] + + # cyclical import + 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 + return [(flattened_key, cast(FileTypes, obj))] + + index += 1 + if is_dict(obj): + try: + # We are at the last entry in the path so we must remove the field + if (len(path)) == index: + item = obj.pop(key) + else: + item = obj[key] + except KeyError: + # Key was not present in the dictionary, this is not indicative of an error + # as the given path may not point to a required field. We also do not want + # to enforce required fields as the API may differ from the spec in some cases. + return [] + if flattened_key is None: + flattened_key = key + else: + flattened_key += f"[{key}]" + return _extract_items( + item, + path, + index=index, + flattened_key=flattened_key, + ) + elif is_list(obj): + if key != "": + return [] + + return flatten( + [ + _extract_items( + item, + path, + index=index, + flattened_key=flattened_key + "[]" if flattened_key is not None else "[]", + ) + for item in obj + ] + ) + + # Something unexpected was passed, just ignore it. + return [] + + +def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) + + +# 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. +# +# 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 +# `is_*_t` is for when you're narrowing a known union type to a specific subset + + +def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]: + return isinstance(obj, tuple) + + +def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]: + return isinstance(obj, tuple) + + +def is_sequence(obj: object) -> TypeGuard[Sequence[object]]: + return isinstance(obj, Sequence) + + +def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]: + return isinstance(obj, Sequence) + + +def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]: + return isinstance(obj, Mapping) + + +def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]: + return isinstance(obj, Mapping) + + +def is_dict(obj: object) -> TypeGuard[dict[object, object]]: + return isinstance(obj, dict) + + +def is_list(obj: object) -> TypeGuard[list[object]]: + return isinstance(obj, list) + + +def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: + return isinstance(obj, Iterable) + + +def deepcopy_minimal(item: _T) -> _T: + """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: + + - mappings, e.g. `dict` + - list + + This is done for performance reasons. + """ + if is_mapping(item): + return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) + if is_list(item): + return cast(_T, [deepcopy_minimal(entry) for entry in item]) + return item + + +# copied from https://github.com/Rapptz/RoboDanny +def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: + size = len(seq) + if size == 0: + return "" + + if size == 1: + return seq[0] + + if size == 2: + return f"{seq[0]} {final} {seq[1]}" + + return delim.join(seq[:-1]) + f" {final} {seq[-1]}" + + +def quote(string: str) -> str: + """Add single quotation marks around the given string. Does *not* do any escaping.""" + return f"'{string}'" + + +def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]: + """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function. + + Useful for enforcing runtime validation of overloaded functions. + + Example usage: + ```py + @overload + def foo(*, a: str) -> str: ... + + + @overload + 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 inner(func: CallableT) -> CallableT: + params = inspect.signature(func).parameters + positional = [ + name + for name, param in params.items() + if param.kind + in { + param.POSITIONAL_ONLY, + param.POSITIONAL_OR_KEYWORD, + } + ] + + @functools.wraps(func) + def wrapper(*args: object, **kwargs: object) -> object: + given_params: set[str] = set() + for i, _ in enumerate(args): + try: + given_params.add(positional[i]) + except IndexError: + raise TypeError( + f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given" + ) from None + + for key in kwargs.keys(): + given_params.add(key) + + for variant in variants: + matches = all((param in given_params for param in variant)) + if matches: + break + else: # no break + if len(variants) > 1: + variations = human_join( + ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants] + ) + msg = f"Missing required arguments; Expected either {variations} arguments to be given" + else: + assert len(variants) > 0 + + # TODO: this error message is not deterministic + missing = list(set(variants[0]) - given_params) + if len(missing) > 1: + msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}" + else: + msg = f"Missing required argument: {quote(missing[0])}" + raise TypeError(msg) + return func(*args, **kwargs) + + return wrapper # type: ignore + + return inner + + +_K = TypeVar("_K") +_V = TypeVar("_V") + + +@overload +def strip_not_given(obj: None) -> None: ... + + +@overload +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 | None) -> object: + """Remove all top-level keys where their values are instances of `NotGiven`""" + if obj is None: + return None + + if not is_mapping(obj): + return obj + + return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)} + + +def coerce_integer(val: str) -> int: + return int(val, base=10) + + +def coerce_float(val: str) -> float: + return float(val) + + +def coerce_boolean(val: str) -> bool: + return val == "true" or val == "1" or val == "on" + + +def maybe_coerce_integer(val: str | None) -> int | None: + if val is None: + return None + return coerce_integer(val) + + +def maybe_coerce_float(val: str | None) -> float | None: + if val is None: + return None + return coerce_float(val) + + +def maybe_coerce_boolean(val: str | None) -> bool | None: + if val is None: + return None + return coerce_boolean(val) + + +def removeprefix(string: str, prefix: str) -> str: + """Remove a prefix from a string. + + Backport of `str.removeprefix` for Python < 3.9 + """ + if string.startswith(prefix): + return string[len(prefix) :] + return string + + +def removesuffix(string: str, suffix: str) -> str: + """Remove a suffix from a string. + + Backport of `str.removesuffix` for Python < 3.9 + """ + if string.endswith(suffix): + return string[: -len(suffix)] + return string + + +def file_from_path(path: str) -> FileTypes: + contents = Path(path).read_bytes() + file_name = os.path.basename(path) + return (file_name, contents) + + +def get_required_header(headers: HeadersLike, header: str) -> str: + lower_header = header.lower() + 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 + 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]: + value = headers.get(normalized_header) + if value: + return value + + raise ValueError(f"Could not find {header} header") + + +def get_async_library() -> str: + try: + return sniffio.current_async_library() + except Exception: + return "false" + + +def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: + """A version of functools.lru_cache that retains the type signature + for the wrapped function arguments. + """ + wrapper = functools.lru_cache( # noqa: TID251 + 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/codex/_version.py b/src/codex/_version.py new file mode 100644 index 00000000..7ee46487 --- /dev/null +++ b/src/codex/_version.py @@ -0,0 +1,4 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +__title__ = "codex" +__version__ = "0.0.1-alpha.0" diff --git a/src/codex/lib/.keep b/src/codex/lib/.keep new file mode 100644 index 00000000..5e2c99fd --- /dev/null +++ b/src/codex/lib/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store custom files to expand the SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/src/codex/py.typed b/src/codex/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/src/codex/resources/__init__.py b/src/codex/resources/__init__.py new file mode 100644 index 00000000..b96b725a --- /dev/null +++ b/src/codex/resources/__init__.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .users import ( + UsersResource, + AsyncUsersResource, + UsersResourceWithRawResponse, + AsyncUsersResourceWithRawResponse, + UsersResourceWithStreamingResponse, + AsyncUsersResourceWithStreamingResponse, +) +from .health import ( + HealthResource, + AsyncHealthResource, + HealthResourceWithRawResponse, + AsyncHealthResourceWithRawResponse, + HealthResourceWithStreamingResponse, + AsyncHealthResourceWithStreamingResponse, +) +from .projects import ( + ProjectsResource, + AsyncProjectsResource, + ProjectsResourceWithRawResponse, + AsyncProjectsResourceWithRawResponse, + ProjectsResourceWithStreamingResponse, + AsyncProjectsResourceWithStreamingResponse, +) +from .organizations import ( + OrganizationsResource, + AsyncOrganizationsResource, + OrganizationsResourceWithRawResponse, + AsyncOrganizationsResourceWithRawResponse, + OrganizationsResourceWithStreamingResponse, + AsyncOrganizationsResourceWithStreamingResponse, +) + +__all__ = [ + "HealthResource", + "AsyncHealthResource", + "HealthResourceWithRawResponse", + "AsyncHealthResourceWithRawResponse", + "HealthResourceWithStreamingResponse", + "AsyncHealthResourceWithStreamingResponse", + "OrganizationsResource", + "AsyncOrganizationsResource", + "OrganizationsResourceWithRawResponse", + "AsyncOrganizationsResourceWithRawResponse", + "OrganizationsResourceWithStreamingResponse", + "AsyncOrganizationsResourceWithStreamingResponse", + "UsersResource", + "AsyncUsersResource", + "UsersResourceWithRawResponse", + "AsyncUsersResourceWithRawResponse", + "UsersResourceWithStreamingResponse", + "AsyncUsersResourceWithStreamingResponse", + "ProjectsResource", + "AsyncProjectsResource", + "ProjectsResourceWithRawResponse", + "AsyncProjectsResourceWithRawResponse", + "ProjectsResourceWithStreamingResponse", + "AsyncProjectsResourceWithStreamingResponse", +] diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py new file mode 100644 index 00000000..29c374e6 --- /dev/null +++ b/src/codex/resources/health.py @@ -0,0 +1,235 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.health_check_response import HealthCheckResponse + +__all__ = ["HealthResource", "AsyncHealthResource"] + + +class HealthResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> HealthResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return HealthResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> HealthResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return HealthResourceWithStreamingResponse(self) + + def check( + self, + *, + # 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, + ) -> HealthCheckResponse: + """Check the health of the application.""" + return self._get( + "/api/health/", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=HealthCheckResponse, + ) + + def db( + self, + *, + # 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, + ) -> HealthCheckResponse: + """Check the database connection.""" + return self._get( + "/api/health/db", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=HealthCheckResponse, + ) + + def weaviate( + self, + *, + # 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, + ) -> HealthCheckResponse: + """Check the weaviate connection.""" + return self._get( + "/api/health/weaviate", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=HealthCheckResponse, + ) + + +class AsyncHealthResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncHealthResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncHealthResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncHealthResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncHealthResourceWithStreamingResponse(self) + + async def check( + self, + *, + # 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, + ) -> HealthCheckResponse: + """Check the health of the application.""" + return await self._get( + "/api/health/", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=HealthCheckResponse, + ) + + async def db( + self, + *, + # 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, + ) -> HealthCheckResponse: + """Check the database connection.""" + return await self._get( + "/api/health/db", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=HealthCheckResponse, + ) + + async def weaviate( + self, + *, + # 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, + ) -> HealthCheckResponse: + """Check the weaviate connection.""" + return await self._get( + "/api/health/weaviate", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=HealthCheckResponse, + ) + + +class HealthResourceWithRawResponse: + def __init__(self, health: HealthResource) -> None: + self._health = health + + self.check = to_raw_response_wrapper( + health.check, + ) + self.db = to_raw_response_wrapper( + health.db, + ) + self.weaviate = to_raw_response_wrapper( + health.weaviate, + ) + + +class AsyncHealthResourceWithRawResponse: + def __init__(self, health: AsyncHealthResource) -> None: + self._health = health + + self.check = async_to_raw_response_wrapper( + health.check, + ) + self.db = async_to_raw_response_wrapper( + health.db, + ) + self.weaviate = async_to_raw_response_wrapper( + health.weaviate, + ) + + +class HealthResourceWithStreamingResponse: + def __init__(self, health: HealthResource) -> None: + self._health = health + + self.check = to_streamed_response_wrapper( + health.check, + ) + self.db = to_streamed_response_wrapper( + health.db, + ) + self.weaviate = to_streamed_response_wrapper( + health.weaviate, + ) + + +class AsyncHealthResourceWithStreamingResponse: + def __init__(self, health: AsyncHealthResource) -> None: + self._health = health + + self.check = async_to_streamed_response_wrapper( + health.check, + ) + self.db = async_to_streamed_response_wrapper( + health.db, + ) + self.weaviate = async_to_streamed_response_wrapper( + health.weaviate, + ) diff --git a/src/codex/resources/organizations/__init__.py b/src/codex/resources/organizations/__init__.py new file mode 100644 index 00000000..9dee3136 --- /dev/null +++ b/src/codex/resources/organizations/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .billing import ( + BillingResource, + AsyncBillingResource, + BillingResourceWithRawResponse, + AsyncBillingResourceWithRawResponse, + BillingResourceWithStreamingResponse, + AsyncBillingResourceWithStreamingResponse, +) +from .organizations import ( + OrganizationsResource, + AsyncOrganizationsResource, + OrganizationsResourceWithRawResponse, + AsyncOrganizationsResourceWithRawResponse, + OrganizationsResourceWithStreamingResponse, + AsyncOrganizationsResourceWithStreamingResponse, +) + +__all__ = [ + "BillingResource", + "AsyncBillingResource", + "BillingResourceWithRawResponse", + "AsyncBillingResourceWithRawResponse", + "BillingResourceWithStreamingResponse", + "AsyncBillingResourceWithStreamingResponse", + "OrganizationsResource", + "AsyncOrganizationsResource", + "OrganizationsResourceWithRawResponse", + "AsyncOrganizationsResourceWithRawResponse", + "OrganizationsResourceWithStreamingResponse", + "AsyncOrganizationsResourceWithStreamingResponse", +] diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing.py new file mode 100644 index 00000000..fb234032 --- /dev/null +++ b/src/codex/resources/organizations/billing.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.organizations.organization_billing_usage_schema import OrganizationBillingUsageSchema +from ...types.organizations.organization_billing_invoices_schema import OrganizationBillingInvoicesSchema + +__all__ = ["BillingResource", "AsyncBillingResource"] + + +class BillingResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BillingResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return BillingResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BillingResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return BillingResourceWithStreamingResponse(self) + + def invoices( + self, + organization_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, + ) -> OrganizationBillingInvoicesSchema: + """ + Get invoices iFrame URL for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/billing/invoices", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingInvoicesSchema, + ) + + def usage( + self, + organization_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, + ) -> OrganizationBillingUsageSchema: + """ + Get usage iFrame URL for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/billing/usage", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingUsageSchema, + ) + + +class AsyncBillingResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBillingResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncBillingResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBillingResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncBillingResourceWithStreamingResponse(self) + + async def invoices( + self, + organization_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, + ) -> OrganizationBillingInvoicesSchema: + """ + Get invoices iFrame URL for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/billing/invoices", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingInvoicesSchema, + ) + + async def usage( + self, + organization_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, + ) -> OrganizationBillingUsageSchema: + """ + Get usage iFrame URL for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/billing/usage", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingUsageSchema, + ) + + +class BillingResourceWithRawResponse: + def __init__(self, billing: BillingResource) -> None: + self._billing = billing + + self.invoices = to_raw_response_wrapper( + billing.invoices, + ) + self.usage = to_raw_response_wrapper( + billing.usage, + ) + + +class AsyncBillingResourceWithRawResponse: + def __init__(self, billing: AsyncBillingResource) -> None: + self._billing = billing + + self.invoices = async_to_raw_response_wrapper( + billing.invoices, + ) + self.usage = async_to_raw_response_wrapper( + billing.usage, + ) + + +class BillingResourceWithStreamingResponse: + def __init__(self, billing: BillingResource) -> None: + self._billing = billing + + self.invoices = to_streamed_response_wrapper( + billing.invoices, + ) + self.usage = to_streamed_response_wrapper( + billing.usage, + ) + + +class AsyncBillingResourceWithStreamingResponse: + def __init__(self, billing: AsyncBillingResource) -> None: + self._billing = billing + + self.invoices = async_to_streamed_response_wrapper( + billing.invoices, + ) + self.usage = async_to_streamed_response_wrapper( + billing.usage, + ) diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py new file mode 100644 index 00000000..3aad77e0 --- /dev/null +++ b/src/codex/resources/organizations/organizations.py @@ -0,0 +1,195 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .billing import ( + BillingResource, + AsyncBillingResource, + BillingResourceWithRawResponse, + AsyncBillingResourceWithRawResponse, + BillingResourceWithStreamingResponse, + AsyncBillingResourceWithStreamingResponse, +) +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.organization_schema_public import OrganizationSchemaPublic + +__all__ = ["OrganizationsResource", "AsyncOrganizationsResource"] + + +class OrganizationsResource(SyncAPIResource): + @cached_property + def billing(self) -> BillingResource: + return BillingResource(self._client) + + @cached_property + def with_raw_response(self) -> OrganizationsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return OrganizationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return OrganizationsResourceWithStreamingResponse(self) + + def retrieve( + self, + organization_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, + ) -> OrganizationSchemaPublic: + """ + Get a single organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationSchemaPublic, + ) + + +class AsyncOrganizationsResource(AsyncAPIResource): + @cached_property + def billing(self) -> AsyncBillingResource: + return AsyncBillingResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncOrganizationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncOrganizationsResourceWithStreamingResponse(self) + + async def retrieve( + self, + organization_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, + ) -> OrganizationSchemaPublic: + """ + Get a single organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationSchemaPublic, + ) + + +class OrganizationsResourceWithRawResponse: + def __init__(self, organizations: OrganizationsResource) -> None: + self._organizations = organizations + + self.retrieve = to_raw_response_wrapper( + organizations.retrieve, + ) + + @cached_property + def billing(self) -> BillingResourceWithRawResponse: + return BillingResourceWithRawResponse(self._organizations.billing) + + +class AsyncOrganizationsResourceWithRawResponse: + def __init__(self, organizations: AsyncOrganizationsResource) -> None: + self._organizations = organizations + + self.retrieve = async_to_raw_response_wrapper( + organizations.retrieve, + ) + + @cached_property + def billing(self) -> AsyncBillingResourceWithRawResponse: + return AsyncBillingResourceWithRawResponse(self._organizations.billing) + + +class OrganizationsResourceWithStreamingResponse: + def __init__(self, organizations: OrganizationsResource) -> None: + self._organizations = organizations + + self.retrieve = to_streamed_response_wrapper( + organizations.retrieve, + ) + + @cached_property + def billing(self) -> BillingResourceWithStreamingResponse: + return BillingResourceWithStreamingResponse(self._organizations.billing) + + +class AsyncOrganizationsResourceWithStreamingResponse: + def __init__(self, organizations: AsyncOrganizationsResource) -> None: + self._organizations = organizations + + self.retrieve = async_to_streamed_response_wrapper( + organizations.retrieve, + ) + + @cached_property + def billing(self) -> AsyncBillingResourceWithStreamingResponse: + return AsyncBillingResourceWithStreamingResponse(self._organizations.billing) diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py new file mode 100644 index 00000000..5250b0b3 --- /dev/null +++ b/src/codex/resources/projects/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .projects import ( + ProjectsResource, + AsyncProjectsResource, + ProjectsResourceWithRawResponse, + AsyncProjectsResourceWithRawResponse, + ProjectsResourceWithStreamingResponse, + AsyncProjectsResourceWithStreamingResponse, +) +from .knowledge import ( + KnowledgeResource, + AsyncKnowledgeResource, + KnowledgeResourceWithRawResponse, + AsyncKnowledgeResourceWithRawResponse, + KnowledgeResourceWithStreamingResponse, + AsyncKnowledgeResourceWithStreamingResponse, +) +from .access_keys import ( + AccessKeysResource, + AsyncAccessKeysResource, + AccessKeysResourceWithRawResponse, + AsyncAccessKeysResourceWithRawResponse, + AccessKeysResourceWithStreamingResponse, + AsyncAccessKeysResourceWithStreamingResponse, +) + +__all__ = [ + "AccessKeysResource", + "AsyncAccessKeysResource", + "AccessKeysResourceWithRawResponse", + "AsyncAccessKeysResourceWithRawResponse", + "AccessKeysResourceWithStreamingResponse", + "AsyncAccessKeysResourceWithStreamingResponse", + "KnowledgeResource", + "AsyncKnowledgeResource", + "KnowledgeResourceWithRawResponse", + "AsyncKnowledgeResourceWithRawResponse", + "KnowledgeResourceWithStreamingResponse", + "AsyncKnowledgeResourceWithStreamingResponse", + "ProjectsResource", + "AsyncProjectsResource", + "ProjectsResourceWithRawResponse", + "AsyncProjectsResourceWithRawResponse", + "ProjectsResourceWithStreamingResponse", + "AsyncProjectsResourceWithStreamingResponse", +] diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py new file mode 100644 index 00000000..7fc21ae9 --- /dev/null +++ b/src/codex/resources/projects/access_keys.py @@ -0,0 +1,594 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +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.projects import access_key_create_params, access_key_update_params +from ...types.projects.access_key_schema import AccessKeySchema +from ...types.projects.access_key_list_response import AccessKeyListResponse + +__all__ = ["AccessKeysResource", "AsyncAccessKeysResource"] + + +class AccessKeysResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> AccessKeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AccessKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AccessKeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AccessKeysResourceWithStreamingResponse(self) + + def create( + self, + project_id: int, + *, + name: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + # 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, + ) -> AccessKeySchema: + """ + Create a new access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + f"/api/projects/{project_id}/access_keys/", + body=maybe_transform( + { + "name": name, + "description": description, + "expires_at": expires_at, + }, + access_key_create_params.AccessKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeySchema, + ) + + def retrieve( + self, + access_key_id: int, + *, + project_id: int, + # 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, + ) -> AccessKeySchema: + """ + Get a single access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + f"/api/projects/{project_id}/access_keys/{access_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeySchema, + ) + + def update( + self, + access_key_id: int, + *, + project_id: int, + name: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + # 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, + ) -> AccessKeySchema: + """ + Update an existing access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._put( + f"/api/projects/{project_id}/access_keys/{access_key_id}", + body=maybe_transform( + { + "name": name, + "description": description, + "expires_at": expires_at, + }, + access_key_update_params.AccessKeyUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeySchema, + ) + + def list( + self, + project_id: int, + *, + # 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, + ) -> AccessKeyListResponse: + """ + List all access keys for a project. + + 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( + f"/api/projects/{project_id}/access_keys/", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeyListResponse, + ) + + def delete( + self, + access_key_id: int, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete an existing access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/api/projects/{project_id}/access_keys/{access_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def revoke( + self, + access_key_id: int, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Revoke an access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncAccessKeysResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAccessKeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncAccessKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAccessKeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncAccessKeysResourceWithStreamingResponse(self) + + async def create( + self, + project_id: int, + *, + name: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + # 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, + ) -> AccessKeySchema: + """ + Create a new access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + f"/api/projects/{project_id}/access_keys/", + body=await async_maybe_transform( + { + "name": name, + "description": description, + "expires_at": expires_at, + }, + access_key_create_params.AccessKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeySchema, + ) + + async def retrieve( + self, + access_key_id: int, + *, + project_id: int, + # 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, + ) -> AccessKeySchema: + """ + Get a single access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + f"/api/projects/{project_id}/access_keys/{access_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeySchema, + ) + + async def update( + self, + access_key_id: int, + *, + project_id: int, + name: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + # 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, + ) -> AccessKeySchema: + """ + Update an existing access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._put( + f"/api/projects/{project_id}/access_keys/{access_key_id}", + body=await async_maybe_transform( + { + "name": name, + "description": description, + "expires_at": expires_at, + }, + access_key_update_params.AccessKeyUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeySchema, + ) + + async def list( + self, + project_id: int, + *, + # 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, + ) -> AccessKeyListResponse: + """ + List all access keys for a project. + + 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( + f"/api/projects/{project_id}/access_keys/", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AccessKeyListResponse, + ) + + async def delete( + self, + access_key_id: int, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete an existing access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/api/projects/{project_id}/access_keys/{access_key_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def revoke( + self, + access_key_id: int, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Revoke an access key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AccessKeysResourceWithRawResponse: + def __init__(self, access_keys: AccessKeysResource) -> None: + self._access_keys = access_keys + + self.create = to_raw_response_wrapper( + access_keys.create, + ) + self.retrieve = to_raw_response_wrapper( + access_keys.retrieve, + ) + self.update = to_raw_response_wrapper( + access_keys.update, + ) + self.list = to_raw_response_wrapper( + access_keys.list, + ) + self.delete = to_raw_response_wrapper( + access_keys.delete, + ) + self.revoke = to_raw_response_wrapper( + access_keys.revoke, + ) + + +class AsyncAccessKeysResourceWithRawResponse: + def __init__(self, access_keys: AsyncAccessKeysResource) -> None: + self._access_keys = access_keys + + self.create = async_to_raw_response_wrapper( + access_keys.create, + ) + self.retrieve = async_to_raw_response_wrapper( + access_keys.retrieve, + ) + self.update = async_to_raw_response_wrapper( + access_keys.update, + ) + self.list = async_to_raw_response_wrapper( + access_keys.list, + ) + self.delete = async_to_raw_response_wrapper( + access_keys.delete, + ) + self.revoke = async_to_raw_response_wrapper( + access_keys.revoke, + ) + + +class AccessKeysResourceWithStreamingResponse: + def __init__(self, access_keys: AccessKeysResource) -> None: + self._access_keys = access_keys + + self.create = to_streamed_response_wrapper( + access_keys.create, + ) + self.retrieve = to_streamed_response_wrapper( + access_keys.retrieve, + ) + self.update = to_streamed_response_wrapper( + access_keys.update, + ) + self.list = to_streamed_response_wrapper( + access_keys.list, + ) + self.delete = to_streamed_response_wrapper( + access_keys.delete, + ) + self.revoke = to_streamed_response_wrapper( + access_keys.revoke, + ) + + +class AsyncAccessKeysResourceWithStreamingResponse: + def __init__(self, access_keys: AsyncAccessKeysResource) -> None: + self._access_keys = access_keys + + self.create = async_to_streamed_response_wrapper( + access_keys.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + access_keys.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + access_keys.update, + ) + self.list = async_to_streamed_response_wrapper( + access_keys.list, + ) + self.delete = async_to_streamed_response_wrapper( + access_keys.delete, + ) + self.revoke = async_to_streamed_response_wrapper( + access_keys.revoke, + ) diff --git a/src/codex/resources/projects/knowledge.py b/src/codex/resources/projects/knowledge.py new file mode 100644 index 00000000..6005f9cc --- /dev/null +++ b/src/codex/resources/projects/knowledge.py @@ -0,0 +1,740 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +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.projects import ( + knowledge_list_params, + knowledge_query_params, + knowledge_create_params, + knowledge_update_params, + knowledge_add_question_params, +) +from ...types.projects.entry import Entry +from ...types.projects.list_knowledge_response import ListKnowledgeResponse + +__all__ = ["KnowledgeResource", "AsyncKnowledgeResource"] + + +class KnowledgeResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> KnowledgeResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return KnowledgeResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> KnowledgeResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return KnowledgeResourceWithStreamingResponse(self) + + def create( + self, + project_id: int, + *, + question: str, + answer: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Create a knowledge entry for a project. + + 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._post( + f"/api/projects/{project_id}/knowledge/", + body=maybe_transform( + { + "question": question, + "answer": answer, + }, + knowledge_create_params.KnowledgeCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def retrieve( + self, + entry_id: str, + *, + project_id: int, + # 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, + ) -> Entry: + """ + Get a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._get( + f"/api/projects/{project_id}/knowledge/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def update( + self, + entry_id: str, + *, + project_id: int, + answer: Optional[str] | NotGiven = NOT_GIVEN, + question: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Update a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._put( + f"/api/projects/{project_id}/knowledge/{entry_id}", + body=maybe_transform( + { + "answer": answer, + "question": question, + }, + knowledge_update_params.KnowledgeUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def list( + self, + project_id: int, + *, + answered_only: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, + unanswered_only: bool | NotGiven = NOT_GIVEN, + # 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, + ) -> ListKnowledgeResponse: + """ + List knowledge entries for a project. + + 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( + f"/api/projects/{project_id}/knowledge/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "answered_only": answered_only, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "unanswered_only": unanswered_only, + }, + knowledge_list_params.KnowledgeListParams, + ), + ), + cast_to=ListKnowledgeResponse, + ) + + def delete( + self, + entry_id: str, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/api/projects/{project_id}/knowledge/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def add_question( + self, + project_id: int, + *, + question: 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, + ) -> Entry: + """ + Add a question to a project. + + Returns: 201 Created if a new question was added 200 OK if the question already + existed + + 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._post( + f"/api/projects/{project_id}/knowledge/add_question", + body=maybe_transform({"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def query( + self, + project_id: int, + *, + question: 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, + ) -> Optional[Entry]: + """ + Query knowledge for a project. + + Returns the matching entry if found and answered, otherwise returns None. This + allows the client to distinguish between: (1) no matching question found + (returns None), and (2) matching question found but not yet answered (returns + Entry with answer=None). + + 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._post( + f"/api/projects/{project_id}/knowledge/query", + body=maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + +class AsyncKnowledgeResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncKnowledgeResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncKnowledgeResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncKnowledgeResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncKnowledgeResourceWithStreamingResponse(self) + + async def create( + self, + project_id: int, + *, + question: str, + answer: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Create a knowledge entry for a project. + + 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._post( + f"/api/projects/{project_id}/knowledge/", + body=await async_maybe_transform( + { + "question": question, + "answer": answer, + }, + knowledge_create_params.KnowledgeCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def retrieve( + self, + entry_id: str, + *, + project_id: int, + # 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, + ) -> Entry: + """ + Get a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._get( + f"/api/projects/{project_id}/knowledge/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def update( + self, + entry_id: str, + *, + project_id: int, + answer: Optional[str] | NotGiven = NOT_GIVEN, + question: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Update a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._put( + f"/api/projects/{project_id}/knowledge/{entry_id}", + body=await async_maybe_transform( + { + "answer": answer, + "question": question, + }, + knowledge_update_params.KnowledgeUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def list( + self, + project_id: int, + *, + answered_only: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, + unanswered_only: bool | NotGiven = NOT_GIVEN, + # 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, + ) -> ListKnowledgeResponse: + """ + List knowledge entries for a project. + + 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( + f"/api/projects/{project_id}/knowledge/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "answered_only": answered_only, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "unanswered_only": unanswered_only, + }, + knowledge_list_params.KnowledgeListParams, + ), + ), + cast_to=ListKnowledgeResponse, + ) + + async def delete( + self, + entry_id: str, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/api/projects/{project_id}/knowledge/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def add_question( + self, + project_id: int, + *, + question: 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, + ) -> Entry: + """ + Add a question to a project. + + Returns: 201 Created if a new question was added 200 OK if the question already + existed + + 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._post( + f"/api/projects/{project_id}/knowledge/add_question", + body=await async_maybe_transform( + {"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def query( + self, + project_id: int, + *, + question: 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, + ) -> Optional[Entry]: + """ + Query knowledge for a project. + + Returns the matching entry if found and answered, otherwise returns None. This + allows the client to distinguish between: (1) no matching question found + (returns None), and (2) matching question found but not yet answered (returns + Entry with answer=None). + + 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._post( + f"/api/projects/{project_id}/knowledge/query", + body=await async_maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + +class KnowledgeResourceWithRawResponse: + def __init__(self, knowledge: KnowledgeResource) -> None: + self._knowledge = knowledge + + self.create = to_raw_response_wrapper( + knowledge.create, + ) + self.retrieve = to_raw_response_wrapper( + knowledge.retrieve, + ) + self.update = to_raw_response_wrapper( + knowledge.update, + ) + self.list = to_raw_response_wrapper( + knowledge.list, + ) + self.delete = to_raw_response_wrapper( + knowledge.delete, + ) + self.add_question = to_raw_response_wrapper( + knowledge.add_question, + ) + self.query = to_raw_response_wrapper( + knowledge.query, + ) + + +class AsyncKnowledgeResourceWithRawResponse: + def __init__(self, knowledge: AsyncKnowledgeResource) -> None: + self._knowledge = knowledge + + self.create = async_to_raw_response_wrapper( + knowledge.create, + ) + self.retrieve = async_to_raw_response_wrapper( + knowledge.retrieve, + ) + self.update = async_to_raw_response_wrapper( + knowledge.update, + ) + self.list = async_to_raw_response_wrapper( + knowledge.list, + ) + self.delete = async_to_raw_response_wrapper( + knowledge.delete, + ) + self.add_question = async_to_raw_response_wrapper( + knowledge.add_question, + ) + self.query = async_to_raw_response_wrapper( + knowledge.query, + ) + + +class KnowledgeResourceWithStreamingResponse: + def __init__(self, knowledge: KnowledgeResource) -> None: + self._knowledge = knowledge + + self.create = to_streamed_response_wrapper( + knowledge.create, + ) + self.retrieve = to_streamed_response_wrapper( + knowledge.retrieve, + ) + self.update = to_streamed_response_wrapper( + knowledge.update, + ) + self.list = to_streamed_response_wrapper( + knowledge.list, + ) + self.delete = to_streamed_response_wrapper( + knowledge.delete, + ) + self.add_question = to_streamed_response_wrapper( + knowledge.add_question, + ) + self.query = to_streamed_response_wrapper( + knowledge.query, + ) + + +class AsyncKnowledgeResourceWithStreamingResponse: + def __init__(self, knowledge: AsyncKnowledgeResource) -> None: + self._knowledge = knowledge + + self.create = async_to_streamed_response_wrapper( + knowledge.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + knowledge.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + knowledge.update, + ) + self.list = async_to_streamed_response_wrapper( + knowledge.list, + ) + self.delete = async_to_streamed_response_wrapper( + knowledge.delete, + ) + self.add_question = async_to_streamed_response_wrapper( + knowledge.add_question, + ) + self.query = async_to_streamed_response_wrapper( + knowledge.query, + ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py new file mode 100644 index 00000000..20e95366 --- /dev/null +++ b/src/codex/resources/projects/projects.py @@ -0,0 +1,659 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ...types import project_list_params, project_create_params, project_update_params +from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) +from ..._compat import cached_property +from .knowledge import ( + KnowledgeResource, + AsyncKnowledgeResource, + KnowledgeResourceWithRawResponse, + AsyncKnowledgeResourceWithRawResponse, + KnowledgeResourceWithStreamingResponse, + AsyncKnowledgeResourceWithStreamingResponse, +) +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 .access_keys import ( + AccessKeysResource, + AsyncAccessKeysResource, + AccessKeysResourceWithRawResponse, + AsyncAccessKeysResourceWithRawResponse, + AccessKeysResourceWithStreamingResponse, + AsyncAccessKeysResourceWithStreamingResponse, +) +from ..._base_client import make_request_options +from ...types.project_list_response import ProjectListResponse +from ...types.project_return_schema import ProjectReturnSchema + +__all__ = ["ProjectsResource", "AsyncProjectsResource"] + + +class ProjectsResource(SyncAPIResource): + @cached_property + def access_keys(self) -> AccessKeysResource: + return AccessKeysResource(self._client) + + @cached_property + def knowledge(self) -> KnowledgeResource: + return KnowledgeResource(self._client) + + @cached_property + def with_raw_response(self) -> ProjectsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return ProjectsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ProjectsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return ProjectsResourceWithStreamingResponse(self) + + def create( + self, + *, + config: project_create_params.Config, + name: str, + organization_id: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Create a new project. + + 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._post( + "/api/projects/", + body=maybe_transform( + { + "config": config, + "name": name, + "organization_id": organization_id, + "description": description, + }, + project_create_params.ProjectCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + def retrieve( + self, + project_id: int, + *, + # 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, + ) -> ProjectReturnSchema: + """ + Get a single project. + + 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( + f"/api/projects/{project_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + def update( + self, + project_id: int, + *, + config: project_update_params.Config, + name: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Update a project. + + 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._put( + f"/api/projects/{project_id}", + body=maybe_transform( + { + "config": config, + "name": name, + "description": description, + }, + project_update_params.ProjectUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + def list( + self, + *, + organization_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, + ) -> ProjectListResponse: + """ + List projects for organization. + + 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/projects/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"organization_id": organization_id}, project_list_params.ProjectListParams), + ), + cast_to=ProjectListResponse, + ) + + def delete( + self, + project_id: int, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete a project. + + 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 + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/api/projects/{project_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def export( + self, + project_id: int, + *, + # 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, + ) -> object: + """ + Export all data for a project as a JSON file. + + 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( + f"/api/projects/{project_id}/export", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=object, + ) + + +class AsyncProjectsResource(AsyncAPIResource): + @cached_property + def access_keys(self) -> AsyncAccessKeysResource: + return AsyncAccessKeysResource(self._client) + + @cached_property + def knowledge(self) -> AsyncKnowledgeResource: + return AsyncKnowledgeResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncProjectsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncProjectsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncProjectsResourceWithStreamingResponse(self) + + async def create( + self, + *, + config: project_create_params.Config, + name: str, + organization_id: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Create a new project. + + 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._post( + "/api/projects/", + body=await async_maybe_transform( + { + "config": config, + "name": name, + "organization_id": organization_id, + "description": description, + }, + project_create_params.ProjectCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + async def retrieve( + self, + project_id: int, + *, + # 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, + ) -> ProjectReturnSchema: + """ + Get a single project. + + 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( + f"/api/projects/{project_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + async def update( + self, + project_id: int, + *, + config: project_update_params.Config, + name: str, + description: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Update a project. + + 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._put( + f"/api/projects/{project_id}", + body=await async_maybe_transform( + { + "config": config, + "name": name, + "description": description, + }, + project_update_params.ProjectUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + async def list( + self, + *, + organization_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, + ) -> ProjectListResponse: + """ + List projects for organization. + + 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/projects/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"organization_id": organization_id}, project_list_params.ProjectListParams + ), + ), + cast_to=ProjectListResponse, + ) + + async def delete( + self, + project_id: int, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete a project. + + 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 + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/api/projects/{project_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def export( + self, + project_id: int, + *, + # 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, + ) -> object: + """ + Export all data for a project as a JSON file. + + 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( + f"/api/projects/{project_id}/export", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=object, + ) + + +class ProjectsResourceWithRawResponse: + def __init__(self, projects: ProjectsResource) -> None: + self._projects = projects + + self.create = to_raw_response_wrapper( + projects.create, + ) + self.retrieve = to_raw_response_wrapper( + projects.retrieve, + ) + self.update = to_raw_response_wrapper( + projects.update, + ) + self.list = to_raw_response_wrapper( + projects.list, + ) + self.delete = to_raw_response_wrapper( + projects.delete, + ) + self.export = to_raw_response_wrapper( + projects.export, + ) + + @cached_property + def access_keys(self) -> AccessKeysResourceWithRawResponse: + return AccessKeysResourceWithRawResponse(self._projects.access_keys) + + @cached_property + def knowledge(self) -> KnowledgeResourceWithRawResponse: + return KnowledgeResourceWithRawResponse(self._projects.knowledge) + + +class AsyncProjectsResourceWithRawResponse: + def __init__(self, projects: AsyncProjectsResource) -> None: + self._projects = projects + + self.create = async_to_raw_response_wrapper( + projects.create, + ) + self.retrieve = async_to_raw_response_wrapper( + projects.retrieve, + ) + self.update = async_to_raw_response_wrapper( + projects.update, + ) + self.list = async_to_raw_response_wrapper( + projects.list, + ) + self.delete = async_to_raw_response_wrapper( + projects.delete, + ) + self.export = async_to_raw_response_wrapper( + projects.export, + ) + + @cached_property + def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: + return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys) + + @cached_property + def knowledge(self) -> AsyncKnowledgeResourceWithRawResponse: + return AsyncKnowledgeResourceWithRawResponse(self._projects.knowledge) + + +class ProjectsResourceWithStreamingResponse: + def __init__(self, projects: ProjectsResource) -> None: + self._projects = projects + + self.create = to_streamed_response_wrapper( + projects.create, + ) + self.retrieve = to_streamed_response_wrapper( + projects.retrieve, + ) + self.update = to_streamed_response_wrapper( + projects.update, + ) + self.list = to_streamed_response_wrapper( + projects.list, + ) + self.delete = to_streamed_response_wrapper( + projects.delete, + ) + self.export = to_streamed_response_wrapper( + projects.export, + ) + + @cached_property + def access_keys(self) -> AccessKeysResourceWithStreamingResponse: + return AccessKeysResourceWithStreamingResponse(self._projects.access_keys) + + @cached_property + def knowledge(self) -> KnowledgeResourceWithStreamingResponse: + return KnowledgeResourceWithStreamingResponse(self._projects.knowledge) + + +class AsyncProjectsResourceWithStreamingResponse: + def __init__(self, projects: AsyncProjectsResource) -> None: + self._projects = projects + + self.create = async_to_streamed_response_wrapper( + projects.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + projects.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + projects.update, + ) + self.list = async_to_streamed_response_wrapper( + projects.list, + ) + self.delete = async_to_streamed_response_wrapper( + projects.delete, + ) + self.export = async_to_streamed_response_wrapper( + projects.export, + ) + + @cached_property + def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: + return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys) + + @cached_property + def knowledge(self) -> AsyncKnowledgeResourceWithStreamingResponse: + return AsyncKnowledgeResourceWithStreamingResponse(self._projects.knowledge) diff --git a/src/codex/resources/users/__init__.py b/src/codex/resources/users/__init__.py new file mode 100644 index 00000000..18ed37eb --- /dev/null +++ b/src/codex/resources/users/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .users import ( + UsersResource, + AsyncUsersResource, + UsersResourceWithRawResponse, + AsyncUsersResourceWithRawResponse, + UsersResourceWithStreamingResponse, + AsyncUsersResourceWithStreamingResponse, +) +from .myself import ( + MyselfResource, + AsyncMyselfResource, + MyselfResourceWithRawResponse, + AsyncMyselfResourceWithRawResponse, + MyselfResourceWithStreamingResponse, + AsyncMyselfResourceWithStreamingResponse, +) + +__all__ = [ + "MyselfResource", + "AsyncMyselfResource", + "MyselfResourceWithRawResponse", + "AsyncMyselfResourceWithRawResponse", + "MyselfResourceWithStreamingResponse", + "AsyncMyselfResourceWithStreamingResponse", + "UsersResource", + "AsyncUsersResource", + "UsersResourceWithRawResponse", + "AsyncUsersResourceWithRawResponse", + "UsersResourceWithStreamingResponse", + "AsyncUsersResourceWithStreamingResponse", +] diff --git a/src/codex/resources/users/myself/__init__.py b/src/codex/resources/users/myself/__init__.py new file mode 100644 index 00000000..62070efa --- /dev/null +++ b/src/codex/resources/users/myself/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .myself import ( + MyselfResource, + AsyncMyselfResource, + MyselfResourceWithRawResponse, + AsyncMyselfResourceWithRawResponse, + MyselfResourceWithStreamingResponse, + AsyncMyselfResourceWithStreamingResponse, +) +from .api_key import ( + APIKeyResource, + AsyncAPIKeyResource, + APIKeyResourceWithRawResponse, + AsyncAPIKeyResourceWithRawResponse, + APIKeyResourceWithStreamingResponse, + AsyncAPIKeyResourceWithStreamingResponse, +) +from .organizations import ( + OrganizationsResource, + AsyncOrganizationsResource, + OrganizationsResourceWithRawResponse, + AsyncOrganizationsResourceWithRawResponse, + OrganizationsResourceWithStreamingResponse, + AsyncOrganizationsResourceWithStreamingResponse, +) + +__all__ = [ + "APIKeyResource", + "AsyncAPIKeyResource", + "APIKeyResourceWithRawResponse", + "AsyncAPIKeyResourceWithRawResponse", + "APIKeyResourceWithStreamingResponse", + "AsyncAPIKeyResourceWithStreamingResponse", + "OrganizationsResource", + "AsyncOrganizationsResource", + "OrganizationsResourceWithRawResponse", + "AsyncOrganizationsResourceWithRawResponse", + "OrganizationsResourceWithStreamingResponse", + "AsyncOrganizationsResourceWithStreamingResponse", + "MyselfResource", + "AsyncMyselfResource", + "MyselfResourceWithRawResponse", + "AsyncMyselfResourceWithRawResponse", + "MyselfResourceWithStreamingResponse", + "AsyncMyselfResourceWithStreamingResponse", +] diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py new file mode 100644 index 00000000..f4108375 --- /dev/null +++ b/src/codex/resources/users/myself/api_key.py @@ -0,0 +1,135 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.users.user_schema import UserSchema + +__all__ = ["APIKeyResource", "AsyncAPIKeyResource"] + + +class APIKeyResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> APIKeyResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return APIKeyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> APIKeyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return APIKeyResourceWithStreamingResponse(self) + + def refresh( + self, + *, + # 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, + ) -> UserSchema: + """Refresh the API key for an authenticated user""" + return self._post( + "/api/users/myself/api-key/refresh", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserSchema, + ) + + +class AsyncAPIKeyResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAPIKeyResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncAPIKeyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAPIKeyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncAPIKeyResourceWithStreamingResponse(self) + + async def refresh( + self, + *, + # 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, + ) -> UserSchema: + """Refresh the API key for an authenticated user""" + return await self._post( + "/api/users/myself/api-key/refresh", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserSchema, + ) + + +class APIKeyResourceWithRawResponse: + def __init__(self, api_key: APIKeyResource) -> None: + self._api_key = api_key + + self.refresh = to_raw_response_wrapper( + api_key.refresh, + ) + + +class AsyncAPIKeyResourceWithRawResponse: + def __init__(self, api_key: AsyncAPIKeyResource) -> None: + self._api_key = api_key + + self.refresh = async_to_raw_response_wrapper( + api_key.refresh, + ) + + +class APIKeyResourceWithStreamingResponse: + def __init__(self, api_key: APIKeyResource) -> None: + self._api_key = api_key + + self.refresh = to_streamed_response_wrapper( + api_key.refresh, + ) + + +class AsyncAPIKeyResourceWithStreamingResponse: + def __init__(self, api_key: AsyncAPIKeyResource) -> None: + self._api_key = api_key + + self.refresh = async_to_streamed_response_wrapper( + api_key.refresh, + ) diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py new file mode 100644 index 00000000..51d0b16b --- /dev/null +++ b/src/codex/resources/users/myself/myself.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 .api_key import ( + APIKeyResource, + AsyncAPIKeyResource, + APIKeyResourceWithRawResponse, + AsyncAPIKeyResourceWithRawResponse, + APIKeyResourceWithStreamingResponse, + AsyncAPIKeyResourceWithStreamingResponse, +) +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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 .organizations import ( + OrganizationsResource, + AsyncOrganizationsResource, + OrganizationsResourceWithRawResponse, + AsyncOrganizationsResourceWithRawResponse, + OrganizationsResourceWithStreamingResponse, + AsyncOrganizationsResourceWithStreamingResponse, +) +from ...._base_client import make_request_options +from ....types.users.user_schema_public import UserSchemaPublic + +__all__ = ["MyselfResource", "AsyncMyselfResource"] + + +class MyselfResource(SyncAPIResource): + @cached_property + def api_key(self) -> APIKeyResource: + return APIKeyResource(self._client) + + @cached_property + def organizations(self) -> OrganizationsResource: + return OrganizationsResource(self._client) + + @cached_property + def with_raw_response(self) -> MyselfResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return MyselfResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> MyselfResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return MyselfResourceWithStreamingResponse(self) + + def retrieve( + self, + *, + # 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, + ) -> UserSchemaPublic: + """Get user info for frontend.""" + return self._get( + "/api/users/myself", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserSchemaPublic, + ) + + +class AsyncMyselfResource(AsyncAPIResource): + @cached_property + def api_key(self) -> AsyncAPIKeyResource: + return AsyncAPIKeyResource(self._client) + + @cached_property + def organizations(self) -> AsyncOrganizationsResource: + return AsyncOrganizationsResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncMyselfResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncMyselfResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMyselfResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncMyselfResourceWithStreamingResponse(self) + + async def retrieve( + self, + *, + # 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, + ) -> UserSchemaPublic: + """Get user info for frontend.""" + return await self._get( + "/api/users/myself", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserSchemaPublic, + ) + + +class MyselfResourceWithRawResponse: + def __init__(self, myself: MyselfResource) -> None: + self._myself = myself + + self.retrieve = to_raw_response_wrapper( + myself.retrieve, + ) + + @cached_property + def api_key(self) -> APIKeyResourceWithRawResponse: + return APIKeyResourceWithRawResponse(self._myself.api_key) + + @cached_property + def organizations(self) -> OrganizationsResourceWithRawResponse: + return OrganizationsResourceWithRawResponse(self._myself.organizations) + + +class AsyncMyselfResourceWithRawResponse: + def __init__(self, myself: AsyncMyselfResource) -> None: + self._myself = myself + + self.retrieve = async_to_raw_response_wrapper( + myself.retrieve, + ) + + @cached_property + def api_key(self) -> AsyncAPIKeyResourceWithRawResponse: + return AsyncAPIKeyResourceWithRawResponse(self._myself.api_key) + + @cached_property + def organizations(self) -> AsyncOrganizationsResourceWithRawResponse: + return AsyncOrganizationsResourceWithRawResponse(self._myself.organizations) + + +class MyselfResourceWithStreamingResponse: + def __init__(self, myself: MyselfResource) -> None: + self._myself = myself + + self.retrieve = to_streamed_response_wrapper( + myself.retrieve, + ) + + @cached_property + def api_key(self) -> APIKeyResourceWithStreamingResponse: + return APIKeyResourceWithStreamingResponse(self._myself.api_key) + + @cached_property + def organizations(self) -> OrganizationsResourceWithStreamingResponse: + return OrganizationsResourceWithStreamingResponse(self._myself.organizations) + + +class AsyncMyselfResourceWithStreamingResponse: + def __init__(self, myself: AsyncMyselfResource) -> None: + self._myself = myself + + self.retrieve = async_to_streamed_response_wrapper( + myself.retrieve, + ) + + @cached_property + def api_key(self) -> AsyncAPIKeyResourceWithStreamingResponse: + return AsyncAPIKeyResourceWithStreamingResponse(self._myself.api_key) + + @cached_property + def organizations(self) -> AsyncOrganizationsResourceWithStreamingResponse: + return AsyncOrganizationsResourceWithStreamingResponse(self._myself.organizations) diff --git a/src/codex/resources/users/myself/organizations.py b/src/codex/resources/users/myself/organizations.py new file mode 100644 index 00000000..5c37dd69 --- /dev/null +++ b/src/codex/resources/users/myself/organizations.py @@ -0,0 +1,135 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.users.myself.user_organizations_schema import UserOrganizationsSchema + +__all__ = ["OrganizationsResource", "AsyncOrganizationsResource"] + + +class OrganizationsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> OrganizationsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return OrganizationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return OrganizationsResourceWithStreamingResponse(self) + + def list( + self, + *, + # 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, + ) -> UserOrganizationsSchema: + """Get the organizations for an authenticated user""" + return self._get( + "/api/users/myself/organizations", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserOrganizationsSchema, + ) + + +class AsyncOrganizationsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncOrganizationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncOrganizationsResourceWithStreamingResponse(self) + + async def list( + self, + *, + # 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, + ) -> UserOrganizationsSchema: + """Get the organizations for an authenticated user""" + return await self._get( + "/api/users/myself/organizations", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserOrganizationsSchema, + ) + + +class OrganizationsResourceWithRawResponse: + def __init__(self, organizations: OrganizationsResource) -> None: + self._organizations = organizations + + self.list = to_raw_response_wrapper( + organizations.list, + ) + + +class AsyncOrganizationsResourceWithRawResponse: + def __init__(self, organizations: AsyncOrganizationsResource) -> None: + self._organizations = organizations + + self.list = async_to_raw_response_wrapper( + organizations.list, + ) + + +class OrganizationsResourceWithStreamingResponse: + def __init__(self, organizations: OrganizationsResource) -> None: + self._organizations = organizations + + self.list = to_streamed_response_wrapper( + organizations.list, + ) + + +class AsyncOrganizationsResourceWithStreamingResponse: + def __init__(self, organizations: AsyncOrganizationsResource) -> None: + self._organizations = organizations + + self.list = async_to_streamed_response_wrapper( + organizations.list, + ) diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py new file mode 100644 index 00000000..4d229677 --- /dev/null +++ b/src/codex/resources/users/users.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from .myself.myself import ( + MyselfResource, + AsyncMyselfResource, + MyselfResourceWithRawResponse, + AsyncMyselfResourceWithRawResponse, + MyselfResourceWithStreamingResponse, + AsyncMyselfResourceWithStreamingResponse, +) + +__all__ = ["UsersResource", "AsyncUsersResource"] + + +class UsersResource(SyncAPIResource): + @cached_property + def myself(self) -> MyselfResource: + return MyselfResource(self._client) + + @cached_property + def with_raw_response(self) -> UsersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return UsersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UsersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return UsersResourceWithStreamingResponse(self) + + +class AsyncUsersResource(AsyncAPIResource): + @cached_property + def myself(self) -> AsyncMyselfResource: + return AsyncMyselfResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncUsersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncUsersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUsersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + """ + return AsyncUsersResourceWithStreamingResponse(self) + + +class UsersResourceWithRawResponse: + def __init__(self, users: UsersResource) -> None: + self._users = users + + @cached_property + def myself(self) -> MyselfResourceWithRawResponse: + return MyselfResourceWithRawResponse(self._users.myself) + + +class AsyncUsersResourceWithRawResponse: + def __init__(self, users: AsyncUsersResource) -> None: + self._users = users + + @cached_property + def myself(self) -> AsyncMyselfResourceWithRawResponse: + return AsyncMyselfResourceWithRawResponse(self._users.myself) + + +class UsersResourceWithStreamingResponse: + def __init__(self, users: UsersResource) -> None: + self._users = users + + @cached_property + def myself(self) -> MyselfResourceWithStreamingResponse: + return MyselfResourceWithStreamingResponse(self._users.myself) + + +class AsyncUsersResourceWithStreamingResponse: + def __init__(self, users: AsyncUsersResource) -> None: + self._users = users + + @cached_property + def myself(self) -> AsyncMyselfResourceWithStreamingResponse: + return AsyncMyselfResourceWithStreamingResponse(self._users.myself) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py new file mode 100644 index 00000000..9a02abc3 --- /dev/null +++ b/src/codex/types/__init__.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .project_list_params import ProjectListParams as ProjectListParams +from .health_check_response import HealthCheckResponse as HealthCheckResponse +from .project_create_params import ProjectCreateParams as ProjectCreateParams +from .project_list_response import ProjectListResponse as ProjectListResponse +from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema +from .project_update_params import ProjectUpdateParams as ProjectUpdateParams +from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic diff --git a/src/codex/types/health_check_response.py b/src/codex/types/health_check_response.py new file mode 100644 index 00000000..7a2655d3 --- /dev/null +++ b/src/codex/types/health_check_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from .._models import BaseModel + +__all__ = ["HealthCheckResponse"] + + +class HealthCheckResponse(BaseModel): + status: str diff --git a/src/codex/types/organization_schema_public.py b/src/codex/types/organization_schema_public.py new file mode 100644 index 00000000..ed245c50 --- /dev/null +++ b/src/codex/types/organization_schema_public.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["OrganizationSchemaPublic"] + + +class OrganizationSchemaPublic(BaseModel): + id: str + + created_at: datetime + + name: str + + updated_at: datetime diff --git a/src/codex/types/organizations/__init__.py b/src/codex/types/organizations/__init__.py new file mode 100644 index 00000000..6d9c59d1 --- /dev/null +++ b/src/codex/types/organizations/__init__.py @@ -0,0 +1,6 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .organization_billing_usage_schema import OrganizationBillingUsageSchema as OrganizationBillingUsageSchema +from .organization_billing_invoices_schema import OrganizationBillingInvoicesSchema as OrganizationBillingInvoicesSchema diff --git a/src/codex/types/organizations/organization_billing_invoices_schema.py b/src/codex/types/organizations/organization_billing_invoices_schema.py new file mode 100644 index 00000000..e588ded0 --- /dev/null +++ b/src/codex/types/organizations/organization_billing_invoices_schema.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from ..._models import BaseModel + +__all__ = ["OrganizationBillingInvoicesSchema"] + + +class OrganizationBillingInvoicesSchema(BaseModel): + invoices_iframe_url: str diff --git a/src/codex/types/organizations/organization_billing_usage_schema.py b/src/codex/types/organizations/organization_billing_usage_schema.py new file mode 100644 index 00000000..8369fb31 --- /dev/null +++ b/src/codex/types/organizations/organization_billing_usage_schema.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from ..._models import BaseModel + +__all__ = ["OrganizationBillingUsageSchema"] + + +class OrganizationBillingUsageSchema(BaseModel): + usage_iframe_url: str diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py new file mode 100644 index 00000000..80882b21 --- /dev/null +++ b/src/codex/types/project_create_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ProjectCreateParams", "Config"] + + +class ProjectCreateParams(TypedDict, total=False): + config: Required[Config] + + name: Required[str] + + organization_id: Required[str] + + description: Optional[str] + + +class Config(TypedDict, total=False): + max_distance: float diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py new file mode 100644 index 00000000..5ca07a82 --- /dev/null +++ b/src/codex/types/project_list_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ProjectListParams"] + + +class ProjectListParams(TypedDict, total=False): + organization_id: Required[str] diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py new file mode 100644 index 00000000..9667cf81 --- /dev/null +++ b/src/codex/types/project_list_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .project_return_schema import ProjectReturnSchema + +__all__ = ["ProjectListResponse"] + +ProjectListResponse: TypeAlias = List[ProjectReturnSchema] diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py new file mode 100644 index 00000000..7f6b06a2 --- /dev/null +++ b/src/codex/types/project_return_schema.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["ProjectReturnSchema", "Config"] + + +class Config(BaseModel): + max_distance: Optional[float] = None + + +class ProjectReturnSchema(BaseModel): + id: int + + config: Config + + created_at: datetime + + created_by_user_id: str + + name: str + + organization_id: str + + updated_at: datetime + + description: Optional[str] = None diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py new file mode 100644 index 00000000..46d747ee --- /dev/null +++ b/src/codex/types/project_update_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ProjectUpdateParams", "Config"] + + +class ProjectUpdateParams(TypedDict, total=False): + config: Required[Config] + + name: Required[str] + + description: Optional[str] + + +class Config(TypedDict, total=False): + max_distance: float diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py new file mode 100644 index 00000000..89e09d61 --- /dev/null +++ b/src/codex/types/projects/__init__.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .entry import Entry as Entry +from .access_key_schema import AccessKeySchema as AccessKeySchema +from .knowledge_list_params import KnowledgeListParams as KnowledgeListParams +from .knowledge_query_params import KnowledgeQueryParams as KnowledgeQueryParams +from .knowledge_create_params import KnowledgeCreateParams as KnowledgeCreateParams +from .knowledge_update_params import KnowledgeUpdateParams as KnowledgeUpdateParams +from .list_knowledge_response import ListKnowledgeResponse as ListKnowledgeResponse +from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams +from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse +from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams +from .knowledge_add_question_params import KnowledgeAddQuestionParams as KnowledgeAddQuestionParams diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py new file mode 100644 index 00000000..1cbc202f --- /dev/null +++ b/src/codex/types/projects/access_key_create_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["AccessKeyCreateParams"] + + +class AccessKeyCreateParams(TypedDict, total=False): + name: Required[str] + + description: Optional[str] + + expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] diff --git a/src/codex/types/projects/access_key_list_response.py b/src/codex/types/projects/access_key_list_response.py new file mode 100644 index 00000000..02db3e05 --- /dev/null +++ b/src/codex/types/projects/access_key_list_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .access_key_schema import AccessKeySchema + +__all__ = ["AccessKeyListResponse"] + +AccessKeyListResponse: TypeAlias = List[AccessKeySchema] diff --git a/src/codex/types/projects/access_key_schema.py b/src/codex/types/projects/access_key_schema.py new file mode 100644 index 00000000..3e023aae --- /dev/null +++ b/src/codex/types/projects/access_key_schema.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["AccessKeySchema"] + + +class AccessKeySchema(BaseModel): + id: int + + token: str + + created_at: datetime + + last_used_at: Optional[datetime] = None + + name: str + + project_id: int + + status: Literal["active", "expired", "revoked"] + + updated_at: datetime + + description: Optional[str] = None + + expires_at: Optional[datetime] = None diff --git a/src/codex/types/projects/access_key_update_params.py b/src/codex/types/projects/access_key_update_params.py new file mode 100644 index 00000000..2f40f335 --- /dev/null +++ b/src/codex/types/projects/access_key_update_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["AccessKeyUpdateParams"] + + +class AccessKeyUpdateParams(TypedDict, total=False): + project_id: Required[int] + + name: Required[str] + + description: Optional[str] + + expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py new file mode 100644 index 00000000..bfa7d278 --- /dev/null +++ b/src/codex/types/projects/entry.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ..._models import BaseModel + +__all__ = ["Entry"] + + +class Entry(BaseModel): + id: str + + created_at: datetime + + question: str + + answer: Optional[str] = None + + answered_at: Optional[datetime] = None diff --git a/src/codex/types/projects/knowledge_add_question_params.py b/src/codex/types/projects/knowledge_add_question_params.py new file mode 100644 index 00000000..97115554 --- /dev/null +++ b/src/codex/types/projects/knowledge_add_question_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["KnowledgeAddQuestionParams"] + + +class KnowledgeAddQuestionParams(TypedDict, total=False): + question: Required[str] diff --git a/src/codex/types/projects/knowledge_create_params.py b/src/codex/types/projects/knowledge_create_params.py new file mode 100644 index 00000000..9c544e6e --- /dev/null +++ b/src/codex/types/projects/knowledge_create_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["KnowledgeCreateParams"] + + +class KnowledgeCreateParams(TypedDict, total=False): + question: Required[str] + + answer: Optional[str] diff --git a/src/codex/types/projects/knowledge_list_params.py b/src/codex/types/projects/knowledge_list_params.py new file mode 100644 index 00000000..45aee13e --- /dev/null +++ b/src/codex/types/projects/knowledge_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["KnowledgeListParams"] + + +class KnowledgeListParams(TypedDict, total=False): + answered_only: bool + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + sort: Literal["created_at", "answered_at"] + + unanswered_only: bool diff --git a/src/codex/types/projects/knowledge_query_params.py b/src/codex/types/projects/knowledge_query_params.py new file mode 100644 index 00000000..2c8097af --- /dev/null +++ b/src/codex/types/projects/knowledge_query_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["KnowledgeQueryParams"] + + +class KnowledgeQueryParams(TypedDict, total=False): + question: Required[str] diff --git a/src/codex/types/projects/knowledge_update_params.py b/src/codex/types/projects/knowledge_update_params.py new file mode 100644 index 00000000..b5e22269 --- /dev/null +++ b/src/codex/types/projects/knowledge_update_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["KnowledgeUpdateParams"] + + +class KnowledgeUpdateParams(TypedDict, total=False): + project_id: Required[int] + + answer: Optional[str] + + question: Optional[str] diff --git a/src/codex/types/projects/list_knowledge_response.py b/src/codex/types/projects/list_knowledge_response.py new file mode 100644 index 00000000..d32f4e38 --- /dev/null +++ b/src/codex/types/projects/list_knowledge_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .entry import Entry +from ..._models import BaseModel + +__all__ = ["ListKnowledgeResponse"] + + +class ListKnowledgeResponse(BaseModel): + entries: List[Entry] + + total_count: int diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py new file mode 100644 index 00000000..85decc6b --- /dev/null +++ b/src/codex/types/users/__init__.py @@ -0,0 +1,6 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .user_schema import UserSchema as UserSchema +from .user_schema_public import UserSchemaPublic as UserSchemaPublic diff --git a/src/codex/types/users/myself/__init__.py b/src/codex/types/users/myself/__init__.py new file mode 100644 index 00000000..f263cd4f --- /dev/null +++ b/src/codex/types/users/myself/__init__.py @@ -0,0 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .user_organizations_schema import UserOrganizationsSchema as UserOrganizationsSchema diff --git a/src/codex/types/users/myself/user_organizations_schema.py b/src/codex/types/users/myself/user_organizations_schema.py new file mode 100644 index 00000000..60ffd5c6 --- /dev/null +++ b/src/codex/types/users/myself/user_organizations_schema.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime + +from ...._models import BaseModel + +__all__ = ["UserOrganizationsSchema", "Organization"] + + +class Organization(BaseModel): + created_at: datetime + + organization_id: str + + updated_at: datetime + + user_id: str + + +class UserOrganizationsSchema(BaseModel): + organizations: List[Organization] diff --git a/src/codex/types/users/user_schema.py b/src/codex/types/users/user_schema.py new file mode 100644 index 00000000..ec6ad541 --- /dev/null +++ b/src/codex/types/users/user_schema.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ..._models import BaseModel + +__all__ = ["UserSchema"] + + +class UserSchema(BaseModel): + id: str + + api_key: str + + api_key_timestamp: datetime + + created_at: datetime + + email: str + + name: Optional[str] = None + + updated_at: datetime diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py new file mode 100644 index 00000000..9becf37d --- /dev/null +++ b/src/codex/types/users/user_schema_public.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["UserSchemaPublic"] + + +class UserSchemaPublic(BaseModel): + id: str + + api_key: str + + email: str + + name: Optional[str] = None diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/__init__.py b/tests/api_resources/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/organizations/__init__.py b/tests/api_resources/organizations/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/organizations/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py new file mode 100644 index 00000000..ccb7dc8a --- /dev/null +++ b/tests/api_resources/organizations/test_billing.py @@ -0,0 +1,174 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBilling: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_invoices(self, client: Codex) -> None: + billing = client.organizations.billing.invoices( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + + @parametrize + def test_raw_response_invoices(self, client: Codex) -> None: + response = client.organizations.billing.with_raw_response.invoices( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + billing = response.parse() + assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + + @parametrize + def test_streaming_response_invoices(self, client: Codex) -> None: + with client.organizations.billing.with_streaming_response.invoices( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + billing = response.parse() + assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_invoices(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.billing.with_raw_response.invoices( + "", + ) + + @parametrize + def test_method_usage(self, client: Codex) -> None: + billing = client.organizations.billing.usage( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + + @parametrize + def test_raw_response_usage(self, client: Codex) -> None: + response = client.organizations.billing.with_raw_response.usage( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + billing = response.parse() + assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + + @parametrize + def test_streaming_response_usage(self, client: Codex) -> None: + with client.organizations.billing.with_streaming_response.usage( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + billing = response.parse() + assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_usage(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.billing.with_raw_response.usage( + "", + ) + + +class TestAsyncBilling: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_invoices(self, async_client: AsyncCodex) -> None: + billing = await async_client.organizations.billing.invoices( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + + @parametrize + async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.billing.with_raw_response.invoices( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + billing = await response.parse() + assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + + @parametrize + async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.billing.with_streaming_response.invoices( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + billing = await response.parse() + assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_invoices(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.billing.with_raw_response.invoices( + "", + ) + + @parametrize + async def test_method_usage(self, async_client: AsyncCodex) -> None: + billing = await async_client.organizations.billing.usage( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + + @parametrize + async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.billing.with_raw_response.usage( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + billing = await response.parse() + assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + + @parametrize + async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.billing.with_streaming_response.usage( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + billing = await response.parse() + assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_usage(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.billing.with_raw_response.usage( + "", + ) diff --git a/tests/api_resources/projects/__init__.py b/tests/api_resources/projects/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/projects/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py new file mode 100644 index 00000000..4fa1636f --- /dev/null +++ b/tests/api_resources/projects/test_access_keys.py @@ -0,0 +1,476 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex._utils import parse_datetime +from codex.types.projects import ( + AccessKeySchema, + AccessKeyListResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAccessKeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Codex) -> None: + access_key = client.projects.access_keys.create( + project_id=0, + name="name", + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + access_key = client.projects.access_keys.create( + project_id=0, + name="name", + description="description", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.access_keys.with_raw_response.create( + project_id=0, + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.access_keys.with_streaming_response.create( + project_id=0, + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + access_key = client.projects.access_keys.retrieve( + access_key_id=0, + project_id=0, + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.access_keys.with_raw_response.retrieve( + access_key_id=0, + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.access_keys.with_streaming_response.retrieve( + access_key_id=0, + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_update(self, client: Codex) -> None: + access_key = client.projects.access_keys.update( + access_key_id=0, + project_id=0, + name="name", + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Codex) -> None: + access_key = client.projects.access_keys.update( + access_key_id=0, + project_id=0, + name="name", + description="description", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Codex) -> None: + response = client.projects.access_keys.with_raw_response.update( + access_key_id=0, + project_id=0, + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Codex) -> None: + with client.projects.access_keys.with_streaming_response.update( + access_key_id=0, + project_id=0, + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_list(self, client: Codex) -> None: + access_key = client.projects.access_keys.list( + 0, + ) + assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.access_keys.with_raw_response.list( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = response.parse() + assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.access_keys.with_streaming_response.list( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = response.parse() + assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Codex) -> None: + access_key = client.projects.access_keys.delete( + access_key_id=0, + project_id=0, + ) + assert access_key is None + + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.access_keys.with_raw_response.delete( + access_key_id=0, + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = response.parse() + assert access_key is None + + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.access_keys.with_streaming_response.delete( + access_key_id=0, + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = response.parse() + assert access_key is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_revoke(self, client: Codex) -> None: + access_key = client.projects.access_keys.revoke( + access_key_id=0, + project_id=0, + ) + assert access_key is None + + @parametrize + def test_raw_response_revoke(self, client: Codex) -> None: + response = client.projects.access_keys.with_raw_response.revoke( + access_key_id=0, + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = response.parse() + assert access_key is None + + @parametrize + def test_streaming_response_revoke(self, client: Codex) -> None: + with client.projects.access_keys.with_streaming_response.revoke( + access_key_id=0, + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = response.parse() + assert access_key is None + + assert cast(Any, response.is_closed) is True + + +class TestAsyncAccessKeys: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.create( + project_id=0, + name="name", + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.create( + project_id=0, + name="name", + description="description", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.access_keys.with_raw_response.create( + project_id=0, + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = await response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.access_keys.with_streaming_response.create( + project_id=0, + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = await response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.retrieve( + access_key_id=0, + project_id=0, + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.access_keys.with_raw_response.retrieve( + access_key_id=0, + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = await response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.access_keys.with_streaming_response.retrieve( + access_key_id=0, + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = await response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_update(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.update( + access_key_id=0, + project_id=0, + name="name", + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.update( + access_key_id=0, + project_id=0, + name="name", + description="description", + expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.access_keys.with_raw_response.update( + access_key_id=0, + project_id=0, + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = await response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: + async with async_client.projects.access_keys.with_streaming_response.update( + access_key_id=0, + project_id=0, + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = await response.parse() + assert_matches_type(AccessKeySchema, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.list( + 0, + ) + assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.access_keys.with_raw_response.list( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = await response.parse() + assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.access_keys.with_streaming_response.list( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = await response.parse() + assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.delete( + access_key_id=0, + project_id=0, + ) + assert access_key is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.access_keys.with_raw_response.delete( + access_key_id=0, + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = await response.parse() + assert access_key is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.access_keys.with_streaming_response.delete( + access_key_id=0, + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = await response.parse() + assert access_key is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_revoke(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.revoke( + access_key_id=0, + project_id=0, + ) + assert access_key is None + + @parametrize + async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.access_keys.with_raw_response.revoke( + access_key_id=0, + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = await response.parse() + assert access_key is None + + @parametrize + async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None: + async with async_client.projects.access_keys.with_streaming_response.revoke( + access_key_id=0, + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = await response.parse() + assert access_key is None + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py new file mode 100644 index 00000000..9052b62d --- /dev/null +++ b/tests/api_resources/projects/test_knowledge.py @@ -0,0 +1,607 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, Optional, cast + +import pytest + +from codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.projects import ( + Entry, + ListKnowledgeResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestKnowledge: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Codex) -> None: + knowledge = client.projects.knowledge.create( + project_id=0, + question="question", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + knowledge = client.projects.knowledge.create( + project_id=0, + question="question", + answer="answer", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.knowledge.with_raw_response.create( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.knowledge.with_streaming_response.create( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + knowledge = client.projects.knowledge.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.knowledge.with_raw_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.knowledge.with_streaming_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.knowledge.with_raw_response.retrieve( + entry_id="", + project_id=0, + ) + + @parametrize + def test_method_update(self, client: Codex) -> None: + knowledge = client.projects.knowledge.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Codex) -> None: + knowledge = client.projects.knowledge.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + answer="answer", + question="question", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Codex) -> None: + response = client.projects.knowledge.with_raw_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Codex) -> None: + with client.projects.knowledge.with_streaming_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.knowledge.with_raw_response.update( + entry_id="", + project_id=0, + ) + + @parametrize + def test_method_list(self, client: Codex) -> None: + knowledge = client.projects.knowledge.list( + project_id=0, + ) + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + knowledge = client.projects.knowledge.list( + project_id=0, + answered_only=True, + limit=1, + offset=0, + order="asc", + sort="created_at", + unanswered_only=True, + ) + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.knowledge.with_raw_response.list( + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = response.parse() + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.knowledge.with_streaming_response.list( + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = response.parse() + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Codex) -> None: + knowledge = client.projects.knowledge.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert knowledge is None + + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.knowledge.with_raw_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = response.parse() + assert knowledge is None + + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.knowledge.with_streaming_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = response.parse() + assert knowledge is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.knowledge.with_raw_response.delete( + entry_id="", + project_id=0, + ) + + @parametrize + def test_method_add_question(self, client: Codex) -> None: + knowledge = client.projects.knowledge.add_question( + project_id=0, + question="question", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_raw_response_add_question(self, client: Codex) -> None: + response = client.projects.knowledge.with_raw_response.add_question( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + def test_streaming_response_add_question(self, client: Codex) -> None: + with client.projects.knowledge.with_streaming_response.add_question( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_query(self, client: Codex) -> None: + knowledge = client.projects.knowledge.query( + project_id=0, + question="question", + ) + assert_matches_type(Optional[Entry], knowledge, path=["response"]) + + @parametrize + def test_raw_response_query(self, client: Codex) -> None: + response = client.projects.knowledge.with_raw_response.query( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = response.parse() + assert_matches_type(Optional[Entry], knowledge, path=["response"]) + + @parametrize + def test_streaming_response_query(self, client: Codex) -> None: + with client.projects.knowledge.with_streaming_response.query( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = response.parse() + assert_matches_type(Optional[Entry], knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncKnowledge: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.create( + project_id=0, + question="question", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.create( + project_id=0, + question="question", + answer="answer", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.knowledge.with_raw_response.create( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.knowledge.with_streaming_response.create( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.knowledge.with_raw_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.knowledge.with_streaming_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.knowledge.with_raw_response.retrieve( + entry_id="", + project_id=0, + ) + + @parametrize + async def test_method_update(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + answer="answer", + question="question", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.knowledge.with_raw_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: + async with async_client.projects.knowledge.with_streaming_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.knowledge.with_raw_response.update( + entry_id="", + project_id=0, + ) + + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.list( + project_id=0, + ) + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.list( + project_id=0, + answered_only=True, + limit=1, + offset=0, + order="asc", + sort="created_at", + unanswered_only=True, + ) + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.knowledge.with_raw_response.list( + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = await response.parse() + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.knowledge.with_streaming_response.list( + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = await response.parse() + assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert knowledge is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.knowledge.with_raw_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = await response.parse() + assert knowledge is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.knowledge.with_streaming_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = await response.parse() + assert knowledge is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.knowledge.with_raw_response.delete( + entry_id="", + project_id=0, + ) + + @parametrize + async def test_method_add_question(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.add_question( + project_id=0, + question="question", + ) + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.knowledge.with_raw_response.add_question( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + @parametrize + async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: + async with async_client.projects.knowledge.with_streaming_response.add_question( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = await response.parse() + assert_matches_type(Entry, knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_query(self, async_client: AsyncCodex) -> None: + knowledge = await async_client.projects.knowledge.query( + project_id=0, + question="question", + ) + assert_matches_type(Optional[Entry], knowledge, path=["response"]) + + @parametrize + async def test_raw_response_query(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.knowledge.with_raw_response.query( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + knowledge = await response.parse() + assert_matches_type(Optional[Entry], knowledge, path=["response"]) + + @parametrize + async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: + async with async_client.projects.knowledge.with_streaming_response.query( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + knowledge = await response.parse() + assert_matches_type(Optional[Entry], knowledge, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py new file mode 100644 index 00000000..be4729ab --- /dev/null +++ b/tests/api_resources/test_health.py @@ -0,0 +1,172 @@ +# 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 codex import Codex, AsyncCodex +from codex.types import HealthCheckResponse +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestHealth: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_check(self, client: Codex) -> None: + health = client.health.check() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + def test_raw_response_check(self, client: Codex) -> None: + response = client.health.with_raw_response.check() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + health = response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + def test_streaming_response_check(self, client: Codex) -> None: + with client.health.with_streaming_response.check() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + health = response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_db(self, client: Codex) -> None: + health = client.health.db() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + def test_raw_response_db(self, client: Codex) -> None: + response = client.health.with_raw_response.db() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + health = response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + def test_streaming_response_db(self, client: Codex) -> None: + with client.health.with_streaming_response.db() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + health = response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_weaviate(self, client: Codex) -> None: + health = client.health.weaviate() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + def test_raw_response_weaviate(self, client: Codex) -> None: + response = client.health.with_raw_response.weaviate() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + health = response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + def test_streaming_response_weaviate(self, client: Codex) -> None: + with client.health.with_streaming_response.weaviate() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + health = response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncHealth: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_check(self, async_client: AsyncCodex) -> None: + health = await async_client.health.check() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + async def test_raw_response_check(self, async_client: AsyncCodex) -> None: + response = await async_client.health.with_raw_response.check() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + health = await response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: + async with async_client.health.with_streaming_response.check() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + health = await response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_db(self, async_client: AsyncCodex) -> None: + health = await async_client.health.db() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + async def test_raw_response_db(self, async_client: AsyncCodex) -> None: + response = await async_client.health.with_raw_response.db() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + health = await response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: + async with async_client.health.with_streaming_response.db() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + health = await response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_weaviate(self, async_client: AsyncCodex) -> None: + health = await async_client.health.weaviate() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None: + response = await async_client.health.with_raw_response.weaviate() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + health = await response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + @parametrize + async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None: + async with async_client.health.with_streaming_response.weaviate() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + health = await response.parse() + assert_matches_type(HealthCheckResponse, health, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py new file mode 100644 index 00000000..2d627c70 --- /dev/null +++ b/tests/api_resources/test_organizations.py @@ -0,0 +1,98 @@ +# 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 codex import Codex, AsyncCodex +from codex.types import OrganizationSchemaPublic +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestOrganizations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + organization = client.organizations.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.organizations.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = response.parse() + assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.organizations.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = response.parse() + assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.with_raw_response.retrieve( + "", + ) + + +class TestAsyncOrganizations: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + organization = await async_client.organizations.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = await response.parse() + assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = await response.parse() + assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py new file mode 100644 index 00000000..7e79d8e8 --- /dev/null +++ b/tests/api_resources/test_projects.py @@ -0,0 +1,461 @@ +# 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 codex import Codex, AsyncCodex +from codex.types import ( + ProjectListResponse, + ProjectReturnSchema, +) +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestProjects: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Codex) -> None: + project = client.projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + project = client.projects.create( + config={"max_distance": 0}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + description="description", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.with_raw_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.with_streaming_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + project = client.projects.retrieve( + 0, + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.with_raw_response.retrieve( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.with_streaming_response.retrieve( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_update(self, client: Codex) -> None: + project = client.projects.update( + project_id=0, + config={}, + name="name", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Codex) -> None: + project = client.projects.update( + project_id=0, + config={"max_distance": 0}, + name="name", + description="description", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Codex) -> None: + response = client.projects.with_raw_response.update( + project_id=0, + config={}, + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Codex) -> None: + with client.projects.with_streaming_response.update( + project_id=0, + config={}, + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_list(self, client: Codex) -> None: + project = client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectListResponse, project, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.with_raw_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectListResponse, project, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.with_streaming_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectListResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Codex) -> None: + project = client.projects.delete( + 0, + ) + assert project is None + + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.with_raw_response.delete( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert project is None + + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.with_streaming_response.delete( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert project is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_export(self, client: Codex) -> None: + project = client.projects.export( + 0, + ) + assert_matches_type(object, project, path=["response"]) + + @parametrize + def test_raw_response_export(self, client: Codex) -> None: + response = client.projects.with_raw_response.export( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(object, project, path=["response"]) + + @parametrize + def test_streaming_response_export(self, client: Codex) -> None: + with client.projects.with_streaming_response.export( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(object, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncProjects: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.create( + config={"max_distance": 0}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + description="description", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.retrieve( + 0, + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.retrieve( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.retrieve( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_update(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.update( + project_id=0, + config={}, + name="name", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.update( + project_id=0, + config={"max_distance": 0}, + name="name", + description="description", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.update( + project_id=0, + config={}, + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.update( + project_id=0, + config={}, + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectListResponse, project, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectListResponse, project, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectListResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.delete( + 0, + ) + assert project is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.delete( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert project is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.delete( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert project is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_export(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.export( + 0, + ) + assert_matches_type(object, project, path=["response"]) + + @parametrize + async def test_raw_response_export(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.export( + 0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(object, project, path=["response"]) + + @parametrize + async def test_streaming_response_export(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.export( + 0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(object, project, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/__init__.py b/tests/api_resources/users/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/users/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/users/myself/__init__.py b/tests/api_resources/users/myself/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/users/myself/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py new file mode 100644 index 00000000..55bba35f --- /dev/null +++ b/tests/api_resources/users/myself/test_api_key.py @@ -0,0 +1,72 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.users import UserSchema + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAPIKey: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_refresh(self, client: Codex) -> None: + api_key = client.users.myself.api_key.refresh() + assert_matches_type(UserSchema, api_key, path=["response"]) + + @parametrize + def test_raw_response_refresh(self, client: Codex) -> None: + response = client.users.myself.api_key.with_raw_response.refresh() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(UserSchema, api_key, path=["response"]) + + @parametrize + def test_streaming_response_refresh(self, client: Codex) -> None: + with client.users.myself.api_key.with_streaming_response.refresh() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(UserSchema, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncAPIKey: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_refresh(self, async_client: AsyncCodex) -> None: + api_key = await async_client.users.myself.api_key.refresh() + assert_matches_type(UserSchema, api_key, path=["response"]) + + @parametrize + async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: + response = await async_client.users.myself.api_key.with_raw_response.refresh() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = await response.parse() + assert_matches_type(UserSchema, api_key, path=["response"]) + + @parametrize + async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None: + async with async_client.users.myself.api_key.with_streaming_response.refresh() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = await response.parse() + assert_matches_type(UserSchema, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py new file mode 100644 index 00000000..da485882 --- /dev/null +++ b/tests/api_resources/users/myself/test_organizations.py @@ -0,0 +1,72 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.users.myself import UserOrganizationsSchema + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestOrganizations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: Codex) -> None: + organization = client.users.myself.organizations.list() + assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.users.myself.organizations.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = response.parse() + assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.users.myself.organizations.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = response.parse() + assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncOrganizations: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + organization = await async_client.users.myself.organizations.list() + assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.users.myself.organizations.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = await response.parse() + assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.users.myself.organizations.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = await response.parse() + assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py new file mode 100644 index 00000000..a206d1f9 --- /dev/null +++ b/tests/api_resources/users/test_myself.py @@ -0,0 +1,72 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.users import UserSchemaPublic + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestMyself: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + myself = client.users.myself.retrieve() + assert_matches_type(UserSchemaPublic, myself, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.users.myself.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + myself = response.parse() + assert_matches_type(UserSchemaPublic, myself, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.users.myself.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + myself = response.parse() + assert_matches_type(UserSchemaPublic, myself, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncMyself: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + myself = await async_client.users.myself.retrieve() + assert_matches_type(UserSchemaPublic, myself, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.users.myself.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + myself = await response.parse() + assert_matches_type(UserSchemaPublic, myself, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.users.myself.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + myself = await response.parse() + assert_matches_type(UserSchemaPublic, myself, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..da139dcc --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import os +import logging +from typing import TYPE_CHECKING, Iterator, AsyncIterator + +import pytest +from pytest_asyncio import is_async_test + +from codex import Codex, AsyncCodex + +if TYPE_CHECKING: + from _pytest.fixtures import FixtureRequest + +pytest.register_assert_rewrite("tests.utils") + +logging.getLogger("codex").setLevel(logging.DEBUG) + + +# 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) + + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + +bearer_token = "My Bearer Token" +api_key = "My API Key" +access_key = "My Access Key" + + +@pytest.fixture(scope="session") +def client(request: FixtureRequest) -> Iterator[Codex]: + strict = getattr(request, "param", True) + if not isinstance(strict, bool): + raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") + + with Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=strict, + ) as client: + yield client + + +@pytest.fixture(scope="session") +async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]: + strict = getattr(request, "param", True) + if not isinstance(strict, bool): + raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") + + async with AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=strict, + ) as client: + yield client diff --git a/tests/sample_file.txt b/tests/sample_file.txt new file mode 100644 index 00000000..af5626b4 --- /dev/null +++ b/tests/sample_file.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 00000000..d36040f6 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,1873 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import gc +import os +import sys +import json +import asyncio +import inspect +import subprocess +import tracemalloc +from typing import Any, Union, cast +from textwrap import dedent +from unittest import mock +from typing_extensions import Literal + +import httpx +import pytest +from respx import MockRouter +from pydantic import ValidationError + +from codex import Codex, AsyncCodex, APIResponseValidationError +from codex._types import Omit +from codex._models import BaseModel, FinalRequestOptions +from codex._constants import RAW_RESPONSE_HEADER +from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError +from codex._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options + +from .utils import update_env + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") +bearer_token = "My Bearer Token" +api_key = "My API Key" +access_key = "My Access Key" + + +def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + url = httpx.URL(request.url) + return dict(url.params) + + +def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: + return 0.1 + + +def _get_open_connections(client: Codex | AsyncCodex) -> int: + transport = client._client._transport + assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) + + pool = transport._pool + return len(pool._requests) + + +class TestCodex: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + + @pytest.mark.respx(base_url=base_url) + def test_raw_response(self, respx_mock: MockRouter) -> None: + respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = self.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: + 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) + 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) + + copied = self.client.copy(bearer_token="another My Bearer Token") + assert copied.bearer_token == "another My Bearer Token" + assert self.client.bearer_token == "My Bearer Token" + + copied = self.client.copy(api_key="another My API Key") + assert copied.api_key == "another My API Key" + assert self.client.api_key == "My API Key" + + copied = self.client.copy(access_key="another My Access Key") + assert copied.access_key == "another My Access Key" + assert self.client.access_key == "My Access Key" + + def test_copy_default_options(self) -> None: + # options that have a default are overridden correctly + copied = self.client.copy(max_retries=7) + assert copied.max_retries == 7 + assert self.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 copied.timeout is None + assert isinstance(self.client.timeout, httpx.Timeout) + + def test_copy_default_headers(self) -> None: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, + ) + assert client.default_headers["X-Foo"] == "bar" + + # does not override the already given value when not specified + copied = client.copy() + assert copied.default_headers["X-Foo"] == "bar" + + # merges already given headers + copied = client.copy(default_headers={"X-Bar": "stainless"}) + assert copied.default_headers["X-Foo"] == "bar" + assert copied.default_headers["X-Bar"] == "stainless" + + # uses new values for any already given headers + copied = client.copy(default_headers={"X-Foo": "stainless"}) + assert copied.default_headers["X-Foo"] == "stainless" + + # set_default_headers + + # completely overrides already set values + copied = client.copy(set_default_headers={}) + assert copied.default_headers.get("X-Foo") is None + + copied = client.copy(set_default_headers={"X-Bar": "Robert"}) + assert copied.default_headers["X-Bar"] == "Robert" + + with pytest.raises( + ValueError, + match="`default_headers` and `set_default_headers` arguments are mutually exclusive", + ): + client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + + def test_copy_default_query(self) -> None: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_query={"foo": "bar"}, + ) + assert _get_params(client)["foo"] == "bar" + + # does not override the already given value when not specified + copied = client.copy() + assert _get_params(copied)["foo"] == "bar" + + # merges already given params + copied = client.copy(default_query={"bar": "stainless"}) + params = _get_params(copied) + assert params["foo"] == "bar" + assert params["bar"] == "stainless" + + # uses new values for any already given headers + copied = client.copy(default_query={"foo": "stainless"}) + assert _get_params(copied)["foo"] == "stainless" + + # set_default_query + + # completely overrides already set values + copied = client.copy(set_default_query={}) + assert _get_params(copied) == {} + + copied = client.copy(set_default_query={"bar": "Robert"}) + assert _get_params(copied)["bar"] == "Robert" + + with pytest.raises( + ValueError, + # TODO: update + match="`default_query` and `set_default_query` arguments are mutually exclusive", + ): + client.copy(set_default_query={}, default_query={"foo": "Bar"}) + + def test_copy_signature(self) -> 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] + ) + copy_signature = inspect.signature(self.client.copy) + exclude_params = {"transport", "proxies", "_strict_response_validation"} + + for name in init_signature.parameters.keys(): + if name in exclude_params: + continue + + 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. + "codex/_legacy_response.py", + "codex/_response.py", + # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. + "codex/_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")) + 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)) + ) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx.Timeout(100.0) + + def test_client_timeout_option(self) -> None: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + timeout=httpx.Timeout(0), + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx.Timeout(0) + + 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: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=http_client, + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx.Timeout(None) + + # no timeout given to the httpx client should not use the httpx default + with httpx.Client() as http_client: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=http_client, + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == DEFAULT_TIMEOUT + + # explicitly passing the default timeout currently results in it being ignored + with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=http_client, + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == DEFAULT_TIMEOUT # our default + + async def test_invalid_http_client(self) -> None: + with pytest.raises(TypeError, match="Invalid `http_client` arg"): + async with httpx.AsyncClient() as http_client: + Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) + + def test_default_headers_option(self) -> None: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, + ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert request.headers.get("x-foo") == "bar" + assert request.headers.get("x-stainless-lang") == "python" + + client2 = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_headers={ + "X-Foo": "stainless", + "X-Stainless-Lang": "my-overriding-header", + }, + ) + request = 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: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_query={"query_param": "bar"}, + ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + url = httpx.URL(request.url) + assert dict(url.params) == {"query_param": "bar"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/foo", + params={"foo": "baz", "query_param": "overridden"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} + + def test_request_extra_json(self) -> None: + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar"}, + extra_json={"baz": False}, + ), + ) + data = json.loads(request.content.decode("utf-8")) + assert data == {"foo": "bar", "baz": False} + + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + extra_json={"baz": False}, + ), + ) + data = json.loads(request.content.decode("utf-8")) + assert data == {"baz": False} + + # `extra_json` takes priority over `json_data` when keys clash + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar", "baz": True}, + extra_json={"baz": 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( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options(extra_headers={"X-Foo": "Foo"}), + ), + ) + 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( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + extra_headers={"X-Bar": "false"}, + ), + ), + ) + assert request.headers.get("X-Bar") == "false" + + def test_request_extra_query(self) -> None: + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + extra_query={"my_query_param": "Foo"}, + ), + ), + ) + params = dict(request.url.params) + assert params == {"my_query_param": "Foo"} + + # if both `query` and `extra_query` are given, they are merged + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + query={"bar": "1"}, + extra_query={"foo": "2"}, + ), + ), + ) + params = dict(request.url.params) + assert params == {"bar": "1", "foo": "2"} + + # `extra_query` takes priority over `query` when keys clash + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + query={"foo": "1"}, + extra_query={"foo": "2"}, + ), + ), + ) + params = dict(request.url.params) + assert params == {"foo": "2"} + + def test_multipart_repeating_array(self, client: Codex) -> None: + request = client._build_request( + FinalRequestOptions.construct( + method="get", + url="/foo", + headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, + json_data={"array": ["foo", "bar"]}, + files=[("foo.txt", b"hello world")], + ) + ) + + assert request.read().split(b"\r\n") == [ + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"foo", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"bar", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="foo.txt"; filename="upload"', + b"Content-Type: application/octet-stream", + b"", + b"hello world", + b"--6b7ba517decee4a450543ea6ae821c82--", + b"", + ] + + @pytest.mark.respx(base_url=base_url) + def test_basic_union_response(self, respx_mock: MockRouter) -> None: + class Model1(BaseModel): + name: str + + class Model2(BaseModel): + foo: str + + 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])) + 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: + """Union of objects with the same field name using a different type""" + + class Model1(BaseModel): + foo: int + + class Model2(BaseModel): + foo: str + + 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])) + 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])) + 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: + """ + Response that sets Content-Type to something other than application/json but returns json data + """ + + class Model(BaseModel): + foo: int + + respx_mock.get("/foo").mock( + return_value=httpx.Response( + 200, + content=json.dumps({"foo": 2}), + headers={"Content-Type": "application/text"}, + ) + ) + + response = self.client.get("/foo", cast_to=Model) + assert isinstance(response, Model) + assert response.foo == 2 + + def test_base_url_setter(self) -> None: + client = Codex( + base_url="https://example.com/from_init", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + assert client.base_url == "https://example.com/from_init/" + + client.base_url = "https://example.com/from_setter" # type: ignore[assignment] + + assert client.base_url == "https://example.com/from_setter/" + + def test_base_url_env(self) -> None: + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + client = Codex( + bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True + ) + assert client.base_url == "http://localhost:5000/from/env/" + + @pytest.mark.parametrize( + "client", + [ + Codex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ), + Codex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=httpx.Client(), + ), + ], + ids=["standard", "custom http client"], + ) + def test_base_url_trailing_slash(self, client: Codex) -> None: + request = client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar"}, + ), + ) + assert request.url == "http://localhost:5000/custom/path/foo" + + @pytest.mark.parametrize( + "client", + [ + Codex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ), + Codex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=httpx.Client(), + ), + ], + ids=["standard", "custom http client"], + ) + def test_base_url_no_trailing_slash(self, client: Codex) -> None: + request = client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar"}, + ), + ) + assert request.url == "http://localhost:5000/custom/path/foo" + + @pytest.mark.parametrize( + "client", + [ + Codex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ), + Codex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=httpx.Client(), + ), + ], + ids=["standard", "custom http client"], + ) + def test_absolute_request_url(self, client: Codex) -> None: + request = client._build_request( + FinalRequestOptions( + method="post", + url="https://myapi.com/foo", + json_data={"foo": "bar"}, + ), + ) + assert request.url == "https://myapi.com/foo" + + def test_copied_client_does_not_close_http(self) -> None: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + assert not client.is_closed() + + copied = client.copy() + assert copied is not client + + del copied + + assert not client.is_closed() + + def test_client_context_manager(self) -> None: + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + with client as c2: + assert c2 is client + assert not c2.is_closed() + assert not client.is_closed() + assert client.is_closed() + + @pytest.mark.respx(base_url=base_url) + def test_client_response_validation_error(self, respx_mock: MockRouter) -> 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) + + assert isinstance(exc.value.__cause__, ValidationError) + + def test_client_max_retries_validation(self) -> None: + with pytest.raises(TypeError, match=r"max_retries cannot be None"): + Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + max_retries=cast(Any, None), + ) + + @pytest.mark.respx(base_url=base_url) + def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: + class Model(BaseModel): + name: str + + respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) + + strict_client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + + with pytest.raises(APIResponseValidationError): + strict_client.get("/foo", cast_to=Model) + + client = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=False, + ) + + response = client.get("/foo", cast_to=Model) + assert isinstance(response, str) # type: ignore[unreachable] + + @pytest.mark.parametrize( + "remaining_retries,retry_after,timeout", + [ + [3, "20", 20], + [3, "0", 0.5], + [3, "-10", 0.5], + [3, "60", 60], + [3, "61", 0.5], + [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], + [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], + [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], + [3, "99999999999999999999999999999999999", 0.5], + [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], + [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 = Codex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + + 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) + assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] + + @mock.patch("codex._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: + respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error")) + + with pytest.raises(APITimeoutError): + self.client.post( + "/api/projects/", + body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + ) + + assert _get_open_connections(self.client) == 0 + + @mock.patch("codex._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: + respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500)) + + with pytest.raises(APIStatusError): + self.client.post( + "/api/projects/", + body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + ) + + assert _get_open_connections(self.client) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("codex._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: Codex, + 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.post("/api/projects/").mock(side_effect=retry_handler) + + response = client.projects.with_raw_response.create( + config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" + ) + + 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("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_omit_retry_count_header(self, client: Codex, 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.post("/api/projects/").mock(side_effect=retry_handler) + + response = client.projects.with_raw_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + 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("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_overwrite_retry_count_header( + self, client: Codex, 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.post("/api/projects/").mock(side_effect=retry_handler) + + response = client.projects.with_raw_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + extra_headers={"x-stainless-retry-count": "42"}, + ) + + assert response.http_request.headers.get("x-stainless-retry-count") == "42" + + +class TestAsyncCodex: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + + @pytest.mark.respx(base_url=base_url) + @pytest.mark.asyncio + async def test_raw_response(self, respx_mock: MockRouter) -> None: + respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + response = await self.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: + 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) + 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) + + copied = self.client.copy(bearer_token="another My Bearer Token") + assert copied.bearer_token == "another My Bearer Token" + assert self.client.bearer_token == "My Bearer Token" + + copied = self.client.copy(api_key="another My API Key") + assert copied.api_key == "another My API Key" + assert self.client.api_key == "My API Key" + + copied = self.client.copy(access_key="another My Access Key") + assert copied.access_key == "another My Access Key" + assert self.client.access_key == "My Access Key" + + def test_copy_default_options(self) -> None: + # options that have a default are overridden correctly + copied = self.client.copy(max_retries=7) + assert copied.max_retries == 7 + assert self.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 copied.timeout is None + assert isinstance(self.client.timeout, httpx.Timeout) + + def test_copy_default_headers(self) -> None: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, + ) + assert client.default_headers["X-Foo"] == "bar" + + # does not override the already given value when not specified + copied = client.copy() + assert copied.default_headers["X-Foo"] == "bar" + + # merges already given headers + copied = client.copy(default_headers={"X-Bar": "stainless"}) + assert copied.default_headers["X-Foo"] == "bar" + assert copied.default_headers["X-Bar"] == "stainless" + + # uses new values for any already given headers + copied = client.copy(default_headers={"X-Foo": "stainless"}) + assert copied.default_headers["X-Foo"] == "stainless" + + # set_default_headers + + # completely overrides already set values + copied = client.copy(set_default_headers={}) + assert copied.default_headers.get("X-Foo") is None + + copied = client.copy(set_default_headers={"X-Bar": "Robert"}) + assert copied.default_headers["X-Bar"] == "Robert" + + with pytest.raises( + ValueError, + match="`default_headers` and `set_default_headers` arguments are mutually exclusive", + ): + client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + + def test_copy_default_query(self) -> None: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_query={"foo": "bar"}, + ) + assert _get_params(client)["foo"] == "bar" + + # does not override the already given value when not specified + copied = client.copy() + assert _get_params(copied)["foo"] == "bar" + + # merges already given params + copied = client.copy(default_query={"bar": "stainless"}) + params = _get_params(copied) + assert params["foo"] == "bar" + assert params["bar"] == "stainless" + + # uses new values for any already given headers + copied = client.copy(default_query={"foo": "stainless"}) + assert _get_params(copied)["foo"] == "stainless" + + # set_default_query + + # completely overrides already set values + copied = client.copy(set_default_query={}) + assert _get_params(copied) == {} + + copied = client.copy(set_default_query={"bar": "Robert"}) + assert _get_params(copied)["bar"] == "Robert" + + with pytest.raises( + ValueError, + # TODO: update + match="`default_query` and `set_default_query` arguments are mutually exclusive", + ): + client.copy(set_default_query={}, default_query={"foo": "Bar"}) + + def test_copy_signature(self) -> 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] + ) + copy_signature = inspect.signature(self.client.copy) + exclude_params = {"transport", "proxies", "_strict_response_validation"} + + for name in init_signature.parameters.keys(): + if name in exclude_params: + continue + + 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. + "codex/_legacy_response.py", + "codex/_response.py", + # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. + "codex/_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")) + 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)) + ) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx.Timeout(100.0) + + async def test_client_timeout_option(self) -> None: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + timeout=httpx.Timeout(0), + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx.Timeout(0) + + 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: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=http_client, + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == httpx.Timeout(None) + + # no timeout given to the httpx client should not use the httpx default + async with httpx.AsyncClient() as http_client: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=http_client, + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == DEFAULT_TIMEOUT + + # explicitly passing the default timeout currently results in it being ignored + async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=http_client, + ) + + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore + assert timeout == DEFAULT_TIMEOUT # our default + + def test_invalid_http_client(self) -> None: + with pytest.raises(TypeError, match="Invalid `http_client` arg"): + with httpx.Client() as http_client: + AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) + + def test_default_headers_option(self) -> None: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, + ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert request.headers.get("x-foo") == "bar" + assert request.headers.get("x-stainless-lang") == "python" + + client2 = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_headers={ + "X-Foo": "stainless", + "X-Stainless-Lang": "my-overriding-header", + }, + ) + request = 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: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + default_query={"query_param": "bar"}, + ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + url = httpx.URL(request.url) + assert dict(url.params) == {"query_param": "bar"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/foo", + params={"foo": "baz", "query_param": "overridden"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} + + def test_request_extra_json(self) -> None: + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar"}, + extra_json={"baz": False}, + ), + ) + data = json.loads(request.content.decode("utf-8")) + assert data == {"foo": "bar", "baz": False} + + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + extra_json={"baz": False}, + ), + ) + data = json.loads(request.content.decode("utf-8")) + assert data == {"baz": False} + + # `extra_json` takes priority over `json_data` when keys clash + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar", "baz": True}, + extra_json={"baz": 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( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options(extra_headers={"X-Foo": "Foo"}), + ), + ) + 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( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + extra_headers={"X-Bar": "false"}, + ), + ), + ) + assert request.headers.get("X-Bar") == "false" + + def test_request_extra_query(self) -> None: + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + extra_query={"my_query_param": "Foo"}, + ), + ), + ) + params = dict(request.url.params) + assert params == {"my_query_param": "Foo"} + + # if both `query` and `extra_query` are given, they are merged + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + query={"bar": "1"}, + extra_query={"foo": "2"}, + ), + ), + ) + params = dict(request.url.params) + assert params == {"bar": "1", "foo": "2"} + + # `extra_query` takes priority over `query` when keys clash + request = self.client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + **make_request_options( + query={"foo": "1"}, + extra_query={"foo": "2"}, + ), + ), + ) + params = dict(request.url.params) + assert params == {"foo": "2"} + + def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None: + request = async_client._build_request( + FinalRequestOptions.construct( + method="get", + url="/foo", + headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, + json_data={"array": ["foo", "bar"]}, + files=[("foo.txt", b"hello world")], + ) + ) + + assert request.read().split(b"\r\n") == [ + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"foo", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"bar", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="foo.txt"; filename="upload"', + b"Content-Type: application/octet-stream", + b"", + b"hello world", + b"--6b7ba517decee4a450543ea6ae821c82--", + b"", + ] + + @pytest.mark.respx(base_url=base_url) + async def test_basic_union_response(self, respx_mock: MockRouter) -> None: + class Model1(BaseModel): + name: str + + class Model2(BaseModel): + foo: str + + 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])) + 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: + """Union of objects with the same field name using a different type""" + + class Model1(BaseModel): + foo: int + + class Model2(BaseModel): + foo: str + + 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])) + 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])) + 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: + """ + Response that sets Content-Type to something other than application/json but returns json data + """ + + class Model(BaseModel): + foo: int + + respx_mock.get("/foo").mock( + return_value=httpx.Response( + 200, + content=json.dumps({"foo": 2}), + headers={"Content-Type": "application/text"}, + ) + ) + + response = await self.client.get("/foo", cast_to=Model) + assert isinstance(response, Model) + assert response.foo == 2 + + def test_base_url_setter(self) -> None: + client = AsyncCodex( + base_url="https://example.com/from_init", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + assert client.base_url == "https://example.com/from_init/" + + client.base_url = "https://example.com/from_setter" # type: ignore[assignment] + + assert client.base_url == "https://example.com/from_setter/" + + def test_base_url_env(self) -> None: + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + client = AsyncCodex( + bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True + ) + assert client.base_url == "http://localhost:5000/from/env/" + + @pytest.mark.parametrize( + "client", + [ + AsyncCodex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ), + AsyncCodex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(), + ), + ], + ids=["standard", "custom http client"], + ) + def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: + request = client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar"}, + ), + ) + assert request.url == "http://localhost:5000/custom/path/foo" + + @pytest.mark.parametrize( + "client", + [ + AsyncCodex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ), + AsyncCodex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(), + ), + ], + ids=["standard", "custom http client"], + ) + def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: + request = client._build_request( + FinalRequestOptions( + method="post", + url="/foo", + json_data={"foo": "bar"}, + ), + ) + assert request.url == "http://localhost:5000/custom/path/foo" + + @pytest.mark.parametrize( + "client", + [ + AsyncCodex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ), + AsyncCodex( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(), + ), + ], + ids=["standard", "custom http client"], + ) + def test_absolute_request_url(self, client: AsyncCodex) -> None: + request = client._build_request( + FinalRequestOptions( + method="post", + url="https://myapi.com/foo", + json_data={"foo": "bar"}, + ), + ) + assert request.url == "https://myapi.com/foo" + + async def test_copied_client_does_not_close_http(self) -> None: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + assert not client.is_closed() + + copied = client.copy() + assert copied is not client + + del copied + + await asyncio.sleep(0.2) + assert not client.is_closed() + + async def test_client_context_manager(self) -> None: + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + async with client as c2: + assert c2 is client + assert not c2.is_closed() + assert not client.is_closed() + assert 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: + 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) + + assert isinstance(exc.value.__cause__, ValidationError) + + async def test_client_max_retries_validation(self) -> None: + with pytest.raises(TypeError, match=r"max_retries cannot be None"): + AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + max_retries=cast(Any, 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 + + respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) + + strict_client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + + with pytest.raises(APIResponseValidationError): + await strict_client.get("/foo", cast_to=Model) + + client = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=False, + ) + + response = await client.get("/foo", cast_to=Model) + assert isinstance(response, str) # type: ignore[unreachable] + + @pytest.mark.parametrize( + "remaining_retries,retry_after,timeout", + [ + [3, "20", 20], + [3, "0", 0.5], + [3, "-10", 0.5], + [3, "60", 60], + [3, "61", 0.5], + [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], + [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], + [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], + [3, "99999999999999999999999999999999999", 0.5], + [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], + [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 = AsyncCodex( + base_url=base_url, + bearer_token=bearer_token, + api_key=api_key, + access_key=access_key, + _strict_response_validation=True, + ) + + 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) + assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] + + @mock.patch("codex._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: + respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error")) + + with pytest.raises(APITimeoutError): + await self.client.post( + "/api/projects/", + body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + ) + + assert _get_open_connections(self.client) == 0 + + @mock.patch("codex._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: + respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500)) + + with pytest.raises(APIStatusError): + await self.client.post( + "/api/projects/", + body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + ) + + assert _get_open_connections(self.client) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + @pytest.mark.asyncio + @pytest.mark.parametrize("failure_mode", ["status", "exception"]) + async def test_retries_taken( + self, + async_client: AsyncCodex, + 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.post("/api/projects/").mock(side_effect=retry_handler) + + response = await client.projects.with_raw_response.create( + config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" + ) + + 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("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + @pytest.mark.asyncio + async def test_omit_retry_count_header( + self, async_client: AsyncCodex, 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.post("/api/projects/").mock(side_effect=retry_handler) + + response = await client.projects.with_raw_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + 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("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + @pytest.mark.asyncio + async def test_overwrite_retry_count_header( + self, async_client: AsyncCodex, 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.post("/api/projects/").mock(side_effect=retry_handler) + + response = await client.projects.with_raw_response.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + extra_headers={"x-stainless-retry-count": "42"}, + ) + + assert response.http_request.headers.get("x-stainless-retry-count") == "42" + + def test_get_platform(self) -> None: + # A previous implementation of asyncify could leave threads unterminated when + # used with nest_asyncio. + # + # Since nest_asyncio.apply() is global and cannot be un-applied, this + # test is run in a separate process to avoid affecting other tests. + test_code = dedent(""" + import asyncio + import nest_asyncio + import threading + + from codex._utils import asyncify + from codex._base_client import get_platform + + async def test_main() -> None: + result = await asyncify(get_platform)() + print(result) + for thread in threading.enumerate(): + print(thread.name) + + nest_asyncio.apply() + asyncio.run(test_main()) + """) + with subprocess.Popen( + [sys.executable, "-c", test_code], + text=True, + ) as process: + try: + process.wait(2) + if process.returncode: + raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") + except subprocess.TimeoutExpired as e: + process.kill() + raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py new file mode 100644 index 00000000..114952b2 --- /dev/null +++ b/tests/test_deepcopy.py @@ -0,0 +1,58 @@ +from codex._utils import deepcopy_minimal + + +def assert_different_identities(obj1: object, obj2: object) -> None: + assert obj1 == obj2 + assert id(obj1) != id(obj2) + + +def test_simple_dict() -> None: + obj1 = {"foo": "bar"} + obj2 = deepcopy_minimal(obj1) + assert_different_identities(obj1, obj2) + + +def test_nested_dict() -> None: + obj1 = {"foo": {"bar": True}} + obj2 = deepcopy_minimal(obj1) + assert_different_identities(obj1, obj2) + assert_different_identities(obj1["foo"], obj2["foo"]) + + +def test_complex_nested_dict() -> None: + obj1 = {"foo": {"bar": [{"hello": "world"}]}} + obj2 = deepcopy_minimal(obj1) + assert_different_identities(obj1, obj2) + assert_different_identities(obj1["foo"], obj2["foo"]) + assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"]) + assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0]) + + +def test_simple_list() -> None: + obj1 = ["a", "b", "c"] + obj2 = deepcopy_minimal(obj1) + assert_different_identities(obj1, obj2) + + +def test_nested_list() -> None: + obj1 = ["a", [1, 2, 3]] + obj2 = deepcopy_minimal(obj1) + assert_different_identities(obj1, obj2) + assert_different_identities(obj1[1], obj2[1]) + + +class MyObject: ... + + +def test_ignores_other_types() -> None: + # custom classes + my_obj = MyObject() + obj1 = {"foo": my_obj} + obj2 = deepcopy_minimal(obj1) + assert_different_identities(obj1, obj2) + assert obj1["foo"] is my_obj + + # tuples + obj3 = ("a", "b") + obj4 = deepcopy_minimal(obj3) + assert obj3 is obj4 diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py new file mode 100644 index 00000000..9edb3f4e --- /dev/null +++ b/tests/test_extract_files.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import Sequence + +import pytest + +from codex._types import FileTypes +from codex._utils import extract_files + + +def test_removes_files_from_input() -> None: + query = {"foo": "bar"} + assert extract_files(query, paths=[]) == [] + assert query == {"foo": "bar"} + + query2 = {"foo": b"Bar", "hello": "world"} + assert extract_files(query2, paths=[["foo"]]) == [("foo", b"Bar")] + assert query2 == {"hello": "world"} + + query3 = {"foo": {"foo": {"bar": b"Bar"}}, "hello": "world"} + assert extract_files(query3, paths=[["foo", "foo", "bar"]]) == [("foo[foo][bar]", b"Bar")] + assert query3 == {"foo": {"foo": {}}, "hello": "world"} + + query4 = {"foo": {"bar": b"Bar", "baz": "foo"}, "hello": "world"} + assert extract_files(query4, paths=[["foo", "bar"]]) == [("foo[bar]", b"Bar")] + assert query4 == {"hello": "world", "foo": {"baz": "foo"}} + + +def test_multiple_files() -> None: + query = {"documents": [{"file": b"My first file"}, {"file": b"My second file"}]} + assert extract_files(query, paths=[["documents", "", "file"]]) == [ + ("documents[][file]", b"My first file"), + ("documents[][file]", b"My second file"), + ] + assert query == {"documents": [{}, {}]} + + +@pytest.mark.parametrize( + "query,paths,expected", + [ + [ + {"foo": {"bar": "baz"}}, + [["foo", "", "bar"]], + [], + ], + [ + {"foo": ["bar", "baz"]}, + [["foo", "bar"]], + [], + ], + [ + {"foo": {"bar": "baz"}}, + [["foo", "foo"]], + [], + ], + ], + ids=["dict expecting array", "array expecting dict", "unknown keys"], +) +def test_ignores_incorrect_paths( + query: dict[str, object], + paths: Sequence[Sequence[str]], + expected: list[tuple[str, FileTypes]], +) -> None: + assert extract_files(query, paths=paths) == expected diff --git a/tests/test_files.py b/tests/test_files.py new file mode 100644 index 00000000..ca20744c --- /dev/null +++ b/tests/test_files.py @@ -0,0 +1,51 @@ +from pathlib import Path + +import anyio +import pytest +from dirty_equals import IsDict, IsList, IsBytes, IsTuple + +from codex._files import to_httpx_files, async_to_httpx_files + +readme_path = Path(__file__).parent.parent.joinpath("README.md") + + +def test_pathlib_includes_file_name() -> None: + result = to_httpx_files({"file": readme_path}) + print(result) + assert result == IsDict({"file": IsTuple("README.md", IsBytes())}) + + +def test_tuple_input() -> None: + result = to_httpx_files([("file", readme_path)]) + print(result) + assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes()))) + + +@pytest.mark.asyncio +async def test_async_pathlib_includes_file_name() -> None: + result = await async_to_httpx_files({"file": readme_path}) + print(result) + assert result == IsDict({"file": IsTuple("README.md", IsBytes())}) + + +@pytest.mark.asyncio +async def test_async_supports_anyio_path() -> None: + result = await async_to_httpx_files({"file": anyio.Path(readme_path)}) + print(result) + assert result == IsDict({"file": IsTuple("README.md", IsBytes())}) + + +@pytest.mark.asyncio +async def test_async_tuple_input() -> None: + result = await async_to_httpx_files([("file", readme_path)]) + print(result) + assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes()))) + + +def test_string_not_allowed() -> None: + with pytest.raises(TypeError, match="Expected file types input to be a FileContent type or to be a tuple"): + to_httpx_files( + { + "file": "foo", # type: ignore + } + ) diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 00000000..26df341f --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,856 @@ +import json +from typing import Any, Dict, List, Union, Optional, cast +from datetime import datetime, timezone +from typing_extensions import Literal, Annotated, TypeAliasType + +import pytest +import pydantic +from pydantic import Field + +from codex._utils import PropertyInfo +from codex._compat import PYDANTIC_V2, parse_obj, model_dump, model_json +from codex._models import BaseModel, construct_type + + +class BasicModel(BaseModel): + foo: str + + +@pytest.mark.parametrize("value", ["hello", 1], ids=["correct type", "mismatched"]) +def test_basic(value: object) -> None: + m = BasicModel.construct(foo=value) + assert m.foo == value + + +def test_directly_nested_model() -> None: + class NestedModel(BaseModel): + nested: BasicModel + + m = NestedModel.construct(nested={"foo": "Foo!"}) + assert m.nested.foo == "Foo!" + + # mismatched types + m = NestedModel.construct(nested="hello!") + assert cast(Any, m.nested) == "hello!" + + +def test_optional_nested_model() -> None: + class NestedModel(BaseModel): + nested: Optional[BasicModel] + + m1 = NestedModel.construct(nested=None) + assert m1.nested is None + + m2 = NestedModel.construct(nested={"foo": "bar"}) + assert m2.nested is not None + assert m2.nested.foo == "bar" + + # mismatched types + m3 = NestedModel.construct(nested={"foo"}) + assert isinstance(cast(Any, m3.nested), set) + assert cast(Any, m3.nested) == {"foo"} + + +def test_list_nested_model() -> None: + class NestedModel(BaseModel): + nested: List[BasicModel] + + m = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}]) + assert m.nested is not None + assert isinstance(m.nested, list) + assert len(m.nested) == 2 + assert m.nested[0].foo == "bar" + assert m.nested[1].foo == "2" + + # mismatched types + m = NestedModel.construct(nested=True) + assert cast(Any, m.nested) is True + + m = NestedModel.construct(nested=[False]) + assert cast(Any, m.nested) == [False] + + +def test_optional_list_nested_model() -> None: + class NestedModel(BaseModel): + nested: Optional[List[BasicModel]] + + m1 = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}]) + assert m1.nested is not None + assert isinstance(m1.nested, list) + assert len(m1.nested) == 2 + assert m1.nested[0].foo == "bar" + assert m1.nested[1].foo == "2" + + m2 = NestedModel.construct(nested=None) + assert m2.nested is None + + # mismatched types + m3 = NestedModel.construct(nested={1}) + assert cast(Any, m3.nested) == {1} + + m4 = NestedModel.construct(nested=[False]) + assert cast(Any, m4.nested) == [False] + + +def test_list_optional_items_nested_model() -> None: + class NestedModel(BaseModel): + nested: List[Optional[BasicModel]] + + m = NestedModel.construct(nested=[None, {"foo": "bar"}]) + assert m.nested is not None + assert isinstance(m.nested, list) + assert len(m.nested) == 2 + assert m.nested[0] is None + assert m.nested[1] is not None + assert m.nested[1].foo == "bar" + + # mismatched types + m3 = NestedModel.construct(nested="foo") + assert cast(Any, m3.nested) == "foo" + + m4 = NestedModel.construct(nested=[False]) + assert cast(Any, m4.nested) == [False] + + +def test_list_mismatched_type() -> None: + class NestedModel(BaseModel): + nested: List[str] + + m = NestedModel.construct(nested=False) + assert cast(Any, m.nested) is False + + +def test_raw_dictionary() -> None: + class NestedModel(BaseModel): + nested: Dict[str, str] + + m = NestedModel.construct(nested={"hello": "world"}) + assert m.nested == {"hello": "world"} + + # mismatched types + m = NestedModel.construct(nested=False) + assert cast(Any, m.nested) is False + + +def test_nested_dictionary_model() -> None: + class NestedModel(BaseModel): + nested: Dict[str, BasicModel] + + m = NestedModel.construct(nested={"hello": {"foo": "bar"}}) + assert isinstance(m.nested, dict) + assert m.nested["hello"].foo == "bar" + + # mismatched types + m = NestedModel.construct(nested={"hello": False}) + assert cast(Any, m.nested["hello"]) is False + + +def test_unknown_fields() -> None: + m1 = BasicModel.construct(foo="foo", unknown=1) + assert m1.foo == "foo" + assert cast(Any, m1).unknown == 1 + + m2 = BasicModel.construct(foo="foo", unknown={"foo_bar": True}) + assert m2.foo == "foo" + assert cast(Any, m2).unknown == {"foo_bar": True} + + assert model_dump(m2) == {"foo": "foo", "unknown": {"foo_bar": True}} + + +def test_strict_validation_unknown_fields() -> None: + class Model(BaseModel): + foo: str + + model = parse_obj(Model, dict(foo="hello!", user="Robert")) + assert model.foo == "hello!" + assert cast(Any, model).user == "Robert" + + assert model_dump(model) == {"foo": "hello!", "user": "Robert"} + + +def test_aliases() -> None: + class Model(BaseModel): + my_field: int = Field(alias="myField") + + m = Model.construct(myField=1) + assert m.my_field == 1 + + # mismatched types + m = Model.construct(myField={"hello": False}) + assert cast(Any, m.my_field) == {"hello": False} + + +def test_repr() -> None: + model = BasicModel(foo="bar") + assert str(model) == "BasicModel(foo='bar')" + assert repr(model) == "BasicModel(foo='bar')" + + +def test_repr_nested_model() -> None: + class Child(BaseModel): + name: str + age: int + + class Parent(BaseModel): + name: str + child: Child + + model = Parent(name="Robert", child=Child(name="Foo", age=5)) + assert str(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))" + assert repr(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))" + + +def test_optional_list() -> None: + class Submodel(BaseModel): + name: str + + class Model(BaseModel): + items: Optional[List[Submodel]] + + m = Model.construct(items=None) + assert m.items is None + + m = Model.construct(items=[]) + assert m.items == [] + + m = Model.construct(items=[{"name": "Robert"}]) + assert m.items is not None + assert len(m.items) == 1 + assert m.items[0].name == "Robert" + + +def test_nested_union_of_models() -> None: + class Submodel1(BaseModel): + bar: bool + + class Submodel2(BaseModel): + thing: str + + class Model(BaseModel): + foo: Union[Submodel1, Submodel2] + + m = Model.construct(foo={"thing": "hello"}) + assert isinstance(m.foo, Submodel2) + assert m.foo.thing == "hello" + + +def test_nested_union_of_mixed_types() -> None: + class Submodel1(BaseModel): + bar: bool + + class Model(BaseModel): + foo: Union[Submodel1, Literal[True], Literal["CARD_HOLDER"]] + + m = Model.construct(foo=True) + assert m.foo is True + + m = Model.construct(foo="CARD_HOLDER") + assert m.foo == "CARD_HOLDER" + + m = Model.construct(foo={"bar": False}) + assert isinstance(m.foo, Submodel1) + assert m.foo.bar is False + + +def test_nested_union_multiple_variants() -> None: + class Submodel1(BaseModel): + bar: bool + + class Submodel2(BaseModel): + thing: str + + class Submodel3(BaseModel): + foo: int + + class Model(BaseModel): + foo: Union[Submodel1, Submodel2, None, Submodel3] + + m = Model.construct(foo={"thing": "hello"}) + assert isinstance(m.foo, Submodel2) + assert m.foo.thing == "hello" + + m = Model.construct(foo=None) + assert m.foo is None + + m = Model.construct() + assert m.foo is None + + m = Model.construct(foo={"foo": "1"}) + assert isinstance(m.foo, Submodel3) + assert m.foo.foo == 1 + + +def test_nested_union_invalid_data() -> None: + class Submodel1(BaseModel): + level: int + + class Submodel2(BaseModel): + name: str + + class Model(BaseModel): + foo: Union[Submodel1, Submodel2] + + m = Model.construct(foo=True) + 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: + assert isinstance(m.foo, Submodel2) + assert m.foo.name == "3" + + +def test_list_of_unions() -> None: + class Submodel1(BaseModel): + level: int + + class Submodel2(BaseModel): + name: str + + class Model(BaseModel): + items: List[Union[Submodel1, Submodel2]] + + m = Model.construct(items=[{"level": 1}, {"name": "Robert"}]) + assert len(m.items) == 2 + assert isinstance(m.items[0], Submodel1) + assert m.items[0].level == 1 + assert isinstance(m.items[1], Submodel2) + assert m.items[1].name == "Robert" + + m = Model.construct(items=[{"level": -1}, 156]) + assert len(m.items) == 2 + assert isinstance(m.items[0], Submodel1) + assert m.items[0].level == -1 + assert cast(Any, m.items[1]) == 156 + + +def test_union_of_lists() -> None: + class SubModel1(BaseModel): + level: int + + class SubModel2(BaseModel): + name: str + + class Model(BaseModel): + items: Union[List[SubModel1], List[SubModel2]] + + # with one valid entry + m = Model.construct(items=[{"name": "Robert"}]) + assert len(m.items) == 1 + assert isinstance(m.items[0], SubModel2) + assert m.items[0].name == "Robert" + + # with two entries pointing to different types + m = Model.construct(items=[{"level": 1}, {"name": "Robert"}]) + assert len(m.items) == 2 + assert isinstance(m.items[0], SubModel1) + assert m.items[0].level == 1 + assert isinstance(m.items[1], SubModel1) + assert cast(Any, m.items[1]).name == "Robert" + + # with two entries pointing to *completely* different types + m = Model.construct(items=[{"level": -1}, 156]) + assert len(m.items) == 2 + assert isinstance(m.items[0], SubModel1) + assert m.items[0].level == -1 + assert cast(Any, m.items[1]) == 156 + + +def test_dict_of_union() -> None: + class SubModel1(BaseModel): + name: str + + class SubModel2(BaseModel): + foo: str + + class Model(BaseModel): + data: Dict[str, Union[SubModel1, SubModel2]] + + m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}}) + assert len(list(m.data.keys())) == 2 + assert isinstance(m.data["hello"], SubModel1) + assert m.data["hello"].name == "there" + assert isinstance(m.data["foo"], SubModel2) + assert m.data["foo"].foo == "bar" + + # TODO: test mismatched type + + +def test_double_nested_union() -> None: + class SubModel1(BaseModel): + name: str + + class SubModel2(BaseModel): + bar: str + + class Model(BaseModel): + data: Dict[str, List[Union[SubModel1, SubModel2]]] + + m = Model.construct(data={"foo": [{"bar": "baz"}, {"name": "Robert"}]}) + assert len(m.data["foo"]) == 2 + + entry1 = m.data["foo"][0] + assert isinstance(entry1, SubModel2) + assert entry1.bar == "baz" + + entry2 = m.data["foo"][1] + assert isinstance(entry2, SubModel1) + assert entry2.name == "Robert" + + # TODO: test mismatched type + + +def test_union_of_dict() -> None: + class SubModel1(BaseModel): + name: str + + class SubModel2(BaseModel): + foo: str + + class Model(BaseModel): + data: Union[Dict[str, SubModel1], Dict[str, SubModel2]] + + m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}}) + assert len(list(m.data.keys())) == 2 + assert isinstance(m.data["hello"], SubModel1) + assert m.data["hello"].name == "there" + assert isinstance(m.data["foo"], SubModel1) + assert cast(Any, m.data["foo"]).foo == "bar" + + +def test_iso8601_datetime() -> None: + class Model(BaseModel): + created_at: datetime + + 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: + expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + + model = Model.construct(created_at="2019-12-27T18:11:19.117Z") + assert model.created_at == expected + assert model_json(model) == expected_json + + model = parse_obj(Model, dict(created_at="2019-12-27T18:11:19.117Z")) + assert model.created_at == expected + assert model_json(model) == expected_json + + +def test_does_not_coerce_int() -> None: + class Model(BaseModel): + bar: int + + assert Model.construct(bar=1).bar == 1 + assert Model.construct(bar=10.9).bar == 10.9 + assert Model.construct(bar="19").bar == "19" # type: ignore[comparison-overlap] + assert Model.construct(bar=False).bar is False + + +def test_int_to_float_safe_conversion() -> None: + class Model(BaseModel): + float_field: float + + m = Model.construct(float_field=10) + assert m.float_field == 10.0 + assert isinstance(m.float_field, float) + + m = Model.construct(float_field=10.12) + assert m.float_field == 10.12 + assert isinstance(m.float_field, float) + + # number too big + m = Model.construct(float_field=2**53 + 1) + assert m.float_field == 2**53 + 1 + assert isinstance(m.float_field, int) + + +def test_deprecated_alias() -> None: + class Model(BaseModel): + resource_id: str = Field(alias="model_id") + + @property + def model_id(self) -> str: + return self.resource_id + + m = Model.construct(model_id="id") + assert m.model_id == "id" + assert m.resource_id == "id" + assert m.resource_id is m.model_id + + m = parse_obj(Model, {"model_id": "id"}) + assert m.model_id == "id" + assert m.resource_id == "id" + assert m.resource_id is m.model_id + + +def test_omitted_fields() -> None: + class Model(BaseModel): + resource_id: Optional[str] = None + + m = Model.construct() + assert "resource_id" not in m.model_fields_set + + m = Model.construct(resource_id=None) + assert "resource_id" in m.model_fields_set + + m = Model.construct(resource_id="foo") + assert "resource_id" in m.model_fields_set + + +def test_to_dict() -> None: + class Model(BaseModel): + foo: Optional[str] = Field(alias="FOO", default=None) + + m = Model(FOO="hello") + assert m.to_dict() == {"FOO": "hello"} + assert m.to_dict(use_api_names=False) == {"foo": "hello"} + + m2 = Model() + assert m2.to_dict() == {} + assert m2.to_dict(exclude_unset=False) == {"FOO": None} + assert m2.to_dict(exclude_unset=False, exclude_none=True) == {} + assert m2.to_dict(exclude_unset=False, exclude_defaults=True) == {} + + m3 = Model(FOO=None) + assert m3.to_dict() == {"FOO": None} + assert m3.to_dict(exclude_none=True) == {} + assert m3.to_dict(exclude_defaults=True) == {} + + 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} + + if not PYDANTIC_V2: + with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): + m.to_dict(warnings=False) + + +def test_forwards_compat_model_dump_method() -> None: + class Model(BaseModel): + foo: Optional[str] = Field(alias="FOO", default=None) + + m = Model(FOO="hello") + assert m.model_dump() == {"foo": "hello"} + assert m.model_dump(include={"bar"}) == {} + assert m.model_dump(exclude={"foo"}) == {} + assert m.model_dump(by_alias=True) == {"FOO": "hello"} + + m2 = Model() + assert m2.model_dump() == {"foo": None} + assert m2.model_dump(exclude_unset=True) == {} + assert m2.model_dump(exclude_none=True) == {} + assert m2.model_dump(exclude_defaults=True) == {} + + m3 = Model(FOO=None) + assert m3.model_dump() == {"foo": None} + assert m3.model_dump(exclude_none=True) == {} + + if not PYDANTIC_V2: + with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): + m.model_dump(round_trip=True) + + with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): + 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) + + m = Model(FOO="hello") + 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: + assert m.to_json(indent=None) == '{"FOO": "hello"}' + + m2 = Model() + assert json.loads(m2.to_json()) == {} + assert json.loads(m2.to_json(exclude_unset=False)) == {"FOO": None} + assert json.loads(m2.to_json(exclude_unset=False, exclude_none=True)) == {} + assert json.loads(m2.to_json(exclude_unset=False, exclude_defaults=True)) == {} + + m3 = Model(FOO=None) + assert json.loads(m3.to_json()) == {"FOO": None} + assert json.loads(m3.to_json(exclude_none=True)) == {} + + if not PYDANTIC_V2: + with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): + m.to_json(warnings=False) + + +def test_forwards_compat_model_dump_json_method() -> None: + class Model(BaseModel): + foo: Optional[str] = Field(alias="FOO", default=None) + + m = Model(FOO="hello") + assert json.loads(m.model_dump_json()) == {"foo": "hello"} + assert json.loads(m.model_dump_json(include={"bar"})) == {} + assert json.loads(m.model_dump_json(include={"foo"})) == {"foo": "hello"} + assert json.loads(m.model_dump_json(by_alias=True)) == {"FOO": "hello"} + + assert m.model_dump_json(indent=2) == '{\n "foo": "hello"\n}' + + m2 = Model() + assert json.loads(m2.model_dump_json()) == {"foo": None} + assert json.loads(m2.model_dump_json(exclude_unset=True)) == {} + assert json.loads(m2.model_dump_json(exclude_none=True)) == {} + assert json.loads(m2.model_dump_json(exclude_defaults=True)) == {} + + m3 = Model(FOO=None) + assert json.loads(m3.model_dump_json()) == {"foo": None} + assert json.loads(m3.model_dump_json(exclude_none=True)) == {} + + if not PYDANTIC_V2: + with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): + m.model_dump_json(round_trip=True) + + with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): + m.model_dump_json(warnings=False) + + +def test_type_compat() -> None: + # our model type can be assigned to Pydantic's model type + + def takes_pydantic(model: pydantic.BaseModel) -> None: # noqa: ARG001 + ... + + class OurModel(BaseModel): + foo: Optional[str] = None + + takes_pydantic(OurModel()) + + +def test_annotated_types() -> None: + class Model(BaseModel): + value: str + + m = construct_type( + value={"value": "foo"}, + type_=cast(Any, Annotated[Model, "random metadata"]), + ) + assert isinstance(m, Model) + assert m.value == "foo" + + +def test_discriminated_unions_invalid_data() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "a", "data": 100}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, A) + assert m.type == "a" + if PYDANTIC_V2: + assert m.data == 100 # type: ignore[comparison-overlap] + else: + # pydantic v1 automatically converts inputs to strings + # if the expected type is a str + assert m.data == "100" + + +def test_discriminated_unions_unknown_variant() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + m = construct_type( + value={"type": "c", "data": None, "new_thing": "bar"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + + # just chooses the first variant + assert isinstance(m, A) + assert m.type == "c" # type: ignore[comparison-overlap] + assert m.data == None # type: ignore[unreachable] + assert m.new_thing == "bar" + + +def test_discriminated_unions_invalid_data_nested_unions() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + class C(BaseModel): + type: Literal["c"] + + data: bool + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "c", "data": "foo"}, + type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, C) + assert m.type == "c" + assert m.data == "foo" # type: ignore[comparison-overlap] + + +def test_discriminated_unions_with_aliases_invalid_data() -> None: + class A(BaseModel): + foo_type: Literal["a"] = Field(alias="type") + + data: str + + class B(BaseModel): + foo_type: Literal["b"] = Field(alias="type") + + data: int + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), + ) + assert isinstance(m, B) + assert m.foo_type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "a", "data": 100}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), + ) + assert isinstance(m, A) + assert m.foo_type == "a" + if PYDANTIC_V2: + assert m.data == 100 # type: ignore[comparison-overlap] + else: + # pydantic v1 automatically converts inputs to strings + # if the expected type is a str + assert m.data == "100" + + +def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["a"] + + data: int + + m = construct_type( + value={"type": "a", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "a" + assert m.data == "foo" # type: ignore[comparison-overlap] + + +def test_discriminated_unions_invalid_data_uses_cache() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + UnionType = cast(Any, Union[A, B]) + + assert not hasattr(UnionType, "__discriminator__") + + m = construct_type( + value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + discriminator = UnionType.__discriminator__ + assert discriminator is not None + + m = construct_type( + value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + # if the discriminator details object stays the same between invocations then + # we hit the cache + assert UnionType.__discriminator__ is discriminator + + +@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +def test_type_alias_type() -> None: + Alias = TypeAliasType("Alias", str) + + 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(not PYDANTIC_V2, 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) diff --git a/tests/test_qs.py b/tests/test_qs.py new file mode 100644 index 00000000..e1c9b4b3 --- /dev/null +++ b/tests/test_qs.py @@ -0,0 +1,78 @@ +from typing import Any, cast +from functools import partial +from urllib.parse import unquote + +import pytest + +from codex._qs import Querystring, stringify + + +def test_empty() -> None: + assert stringify({}) == "" + assert stringify({"a": {}}) == "" + assert stringify({"a": {"b": {"c": {}}}}) == "" + + +def test_basic() -> None: + assert stringify({"a": 1}) == "a=1" + assert stringify({"a": "b"}) == "a=b" + assert stringify({"a": True}) == "a=true" + assert stringify({"a": False}) == "a=false" + assert stringify({"a": 1.23456}) == "a=1.23456" + assert stringify({"a": None}) == "" + + +@pytest.mark.parametrize("method", ["class", "function"]) +def test_nested_dotted(method: str) -> None: + if method == "class": + serialise = Querystring(nested_format="dots").stringify + else: + serialise = partial(stringify, nested_format="dots") + + assert unquote(serialise({"a": {"b": "c"}})) == "a.b=c" + assert unquote(serialise({"a": {"b": "c", "d": "e", "f": "g"}})) == "a.b=c&a.d=e&a.f=g" + assert unquote(serialise({"a": {"b": {"c": {"d": "e"}}}})) == "a.b.c.d=e" + assert unquote(serialise({"a": {"b": True}})) == "a.b=true" + + +def test_nested_brackets() -> None: + assert unquote(stringify({"a": {"b": "c"}})) == "a[b]=c" + assert unquote(stringify({"a": {"b": "c", "d": "e", "f": "g"}})) == "a[b]=c&a[d]=e&a[f]=g" + assert unquote(stringify({"a": {"b": {"c": {"d": "e"}}}})) == "a[b][c][d]=e" + assert unquote(stringify({"a": {"b": True}})) == "a[b]=true" + + +@pytest.mark.parametrize("method", ["class", "function"]) +def test_array_comma(method: str) -> None: + if method == "class": + serialise = Querystring(array_format="comma").stringify + else: + serialise = partial(stringify, array_format="comma") + + assert unquote(serialise({"in": ["foo", "bar"]})) == "in=foo,bar" + assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b]=true,false" + assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b]=true,false,true" + + +def test_array_repeat() -> None: + assert unquote(stringify({"in": ["foo", "bar"]})) == "in=foo&in=bar" + assert unquote(stringify({"a": {"b": [True, False]}})) == "a[b]=true&a[b]=false" + assert unquote(stringify({"a": {"b": [True, False, None, True]}})) == "a[b]=true&a[b]=false&a[b]=true" + assert unquote(stringify({"in": ["foo", {"b": {"c": ["d", "e"]}}]})) == "in=foo&in[b][c]=d&in[b][c]=e" + + +@pytest.mark.parametrize("method", ["class", "function"]) +def test_array_brackets(method: str) -> None: + if method == "class": + serialise = Querystring(array_format="brackets").stringify + else: + serialise = partial(stringify, array_format="brackets") + + assert unquote(serialise({"in": ["foo", "bar"]})) == "in[]=foo&in[]=bar" + assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b][]=true&a[b][]=false" + assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b][]=true&a[b][]=false&a[b][]=true" + + +def test_unknown_array_format() -> None: + with pytest.raises(NotImplementedError, match="Unknown array_format value: foo, choose from comma, repeat"): + stringify({"a": ["foo", "bar"]}, array_format=cast(Any, "foo")) diff --git a/tests/test_required_args.py b/tests/test_required_args.py new file mode 100644 index 00000000..8a160534 --- /dev/null +++ b/tests/test_required_args.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import pytest + +from codex._utils import required_args + + +def test_too_many_positional_params() -> None: + @required_args(["a"]) + def foo(a: str | None = None) -> str | None: + return a + + with pytest.raises(TypeError, match=r"foo\(\) takes 1 argument\(s\) but 2 were given"): + foo("a", "b") # type: ignore + + +def test_positional_param() -> None: + @required_args(["a"]) + def foo(a: str | None = None) -> str | None: + return a + + assert foo("a") == "a" + assert foo(None) is None + assert foo(a="b") == "b" + + with pytest.raises(TypeError, match="Missing required argument: 'a'"): + foo() + + +def test_keyword_only_param() -> None: + @required_args(["a"]) + def foo(*, a: str | None = None) -> str | None: + return a + + assert foo(a="a") == "a" + assert foo(a=None) is None + assert foo(a="b") == "b" + + with pytest.raises(TypeError, match="Missing required argument: 'a'"): + foo() + + +def test_multiple_params() -> None: + @required_args(["a", "b", "c"]) + def foo(a: str = "", *, b: str = "", c: str = "") -> str | None: + return f"{a} {b} {c}" + + assert foo(a="a", b="b", c="c") == "a b c" + + error_message = r"Missing required arguments.*" + + with pytest.raises(TypeError, match=error_message): + foo() + + with pytest.raises(TypeError, match=error_message): + foo(a="a") + + with pytest.raises(TypeError, match=error_message): + foo(b="b") + + with pytest.raises(TypeError, match=error_message): + foo(c="c") + + with pytest.raises(TypeError, match=r"Missing required argument: 'a'"): + foo(b="a", c="c") + + with pytest.raises(TypeError, match=r"Missing required argument: 'b'"): + foo("a", c="c") + + +def test_multiple_variants() -> None: + @required_args(["a"], ["b"]) + def foo(*, a: str | None = None, b: str | None = None) -> str | None: + return a if a is not None else b + + assert foo(a="foo") == "foo" + assert foo(b="bar") == "bar" + assert foo(a=None) is None + assert foo(b=None) is None + + # TODO: this error message could probably be improved + with pytest.raises( + TypeError, + match=r"Missing required arguments; Expected either \('a'\) or \('b'\) arguments to be given", + ): + foo() + + +def test_multiple_params_multiple_variants() -> None: + @required_args(["a", "b"], ["c"]) + def foo(*, a: str | None = None, b: str | None = None, c: str | None = None) -> str | None: + if a is not None: + return a + if b is not None: + return b + return c + + error_message = r"Missing required arguments; Expected either \('a' and 'b'\) or \('c'\) arguments to be given" + + with pytest.raises(TypeError, match=error_message): + foo(a="foo") + + with pytest.raises(TypeError, match=error_message): + foo(b="bar") + + with pytest.raises(TypeError, match=error_message): + foo() + + assert foo(a=None, b="bar") == "bar" + assert foo(c=None) is None + assert foo(c="foo") == "foo" diff --git a/tests/test_response.py b/tests/test_response.py new file mode 100644 index 00000000..224eb22e --- /dev/null +++ b/tests/test_response.py @@ -0,0 +1,277 @@ +import json +from typing import Any, List, Union, cast +from typing_extensions import Annotated + +import httpx +import pytest +import pydantic + +from codex import Codex, BaseModel, AsyncCodex +from codex._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + BinaryAPIResponse, + AsyncBinaryAPIResponse, + extract_response_type, +) +from codex._streaming import Stream +from codex._base_client import FinalRequestOptions + + +class ConcreteBaseAPIResponse(APIResponse[bytes]): ... + + +class ConcreteAPIResponse(APIResponse[List[str]]): ... + + +class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ... + + +def test_extract_response_type_direct_classes() -> None: + assert extract_response_type(BaseAPIResponse[str]) == str + assert extract_response_type(APIResponse[str]) == str + assert extract_response_type(AsyncAPIResponse[str]) == str + + +def test_extract_response_type_direct_class_missing_type_arg() -> None: + with pytest.raises( + RuntimeError, + match="Expected type to have a type argument at index 0 but it did not", + ): + extract_response_type(AsyncAPIResponse) + + +def test_extract_response_type_concrete_subclasses() -> None: + assert extract_response_type(ConcreteBaseAPIResponse) == bytes + assert extract_response_type(ConcreteAPIResponse) == List[str] + assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response + + +def test_extract_response_type_binary_response() -> None: + assert extract_response_type(BinaryAPIResponse) == bytes + assert extract_response_type(AsyncBinaryAPIResponse) == bytes + + +class PydanticModel(pydantic.BaseModel): ... + + +def test_response_parse_mismatched_basemodel(client: Codex) -> None: + response = APIResponse( + raw=httpx.Response(200, content=b"foo"), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + with pytest.raises( + TypeError, + match="Pydantic models must subclass our base model type, e.g. `from codex import BaseModel`", + ): + response.parse(to=PydanticModel) + + +@pytest.mark.asyncio +async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCodex) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=b"foo"), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + with pytest.raises( + TypeError, + match="Pydantic models must subclass our base model type, e.g. `from codex import BaseModel`", + ): + await response.parse(to=PydanticModel) + + +def test_response_parse_custom_stream(client: Codex) -> None: + response = APIResponse( + raw=httpx.Response(200, content=b"foo"), + client=client, + stream=True, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + stream = response.parse(to=Stream[int]) + assert stream._cast_to == int + + +@pytest.mark.asyncio +async def test_async_response_parse_custom_stream(async_client: AsyncCodex) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=b"foo"), + client=async_client, + stream=True, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + stream = await response.parse(to=Stream[int]) + assert stream._cast_to == int + + +class CustomModel(BaseModel): + foo: str + bar: int + + +def test_response_parse_custom_model(client: Codex) -> None: + response = APIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=CustomModel) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +@pytest.mark.asyncio +async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse(to=CustomModel) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +def test_response_parse_annotated_type(client: Codex) -> None: + response = APIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +async def test_async_response_parse_annotated_type(async_client: AsyncCodex) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + 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: Codex, 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: AsyncCodex, 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: Codex) -> 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: AsyncCodex) -> 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_streaming.py b/tests/test_streaming.py new file mode 100644 index 00000000..443b5aa5 --- /dev/null +++ b/tests/test_streaming.py @@ -0,0 +1,248 @@ +from __future__ import annotations + +from typing import Iterator, AsyncIterator + +import httpx +import pytest + +from codex import Codex, AsyncCodex +from codex._streaming import Stream, AsyncStream, ServerSentEvent + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_basic(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b"event: completion\n" + yield b'data: {"foo":true}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "completion" + assert sse.json() == {"foo": True} + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_data_missing_event(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b'data: {"foo":true}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"foo": True} + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_event_missing_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.data == "" + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_events(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"\n" + yield b"event: completion\n" + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.data == "" + + sse = await iter_next(iterator) + assert sse.event == "completion" + assert sse.data == "" + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_events_with_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b'data: {"foo":true}\n' + yield b"\n" + yield b"event: completion\n" + yield b'data: {"bar":false}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.json() == {"foo": True} + + sse = await iter_next(iterator) + assert sse.event == "completion" + assert sse.json() == {"bar": False} + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_data_lines_with_empty_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"data: {\n" + yield b'data: "foo":\n' + yield b"data: \n" + yield b"data:\n" + yield b"data: true}\n" + yield b"\n\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.json() == {"foo": True} + assert sse.data == '{\n"foo":\n\n\ntrue}' + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_data_json_escaped_double_new_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b'data: {"foo": "my long\\n\\ncontent"}' + yield b"\n\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.json() == {"foo": "my long\n\ncontent"} + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_data_lines(sync: bool, client: Codex, async_client: AsyncCodex) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"data: {\n" + yield b'data: "foo":\n' + yield b"data: true}\n" + yield b"\n\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.json() == {"foo": True} + + await assert_empty_iter(iterator) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_special_new_line_character( + sync: bool, + client: Codex, + async_client: AsyncCodex, +) -> None: + def body() -> Iterator[bytes]: + yield b'data: {"content":" culpa"}\n' + yield b"\n" + yield b'data: {"content":" \xe2\x80\xa8"}\n' + yield b"\n" + yield b'data: {"content":"foo"}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": " culpa"} + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": " 
"} + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": "foo"} + + await assert_empty_iter(iterator) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multi_byte_character_multiple_chunks( + sync: bool, + client: Codex, + async_client: AsyncCodex, +) -> None: + def body() -> Iterator[bytes]: + yield b'data: {"content":"' + # bytes taken from the string 'известни' and arbitrarily split + # so that some multi-byte characters span multiple chunks + yield b"\xd0" + yield b"\xb8\xd0\xb7\xd0" + yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8" + yield b'"}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": "известни"} + + +async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]: + for chunk in iter: + yield chunk + + +async def iter_next(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> ServerSentEvent: + if isinstance(iter, AsyncIterator): + return await iter.__anext__() + + return next(iter) + + +async def assert_empty_iter(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> None: + with pytest.raises((StopAsyncIteration, RuntimeError)): + await iter_next(iter) + + +def make_event_iterator( + content: Iterator[bytes], + *, + sync: bool, + client: Codex, + async_client: AsyncCodex, +) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: + if sync: + return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() + + return AsyncStream( + cast_to=object, client=async_client, response=httpx.Response(200, content=to_aiter(content)) + )._iter_events() diff --git a/tests/test_transform.py b/tests/test_transform.py new file mode 100644 index 00000000..2e918885 --- /dev/null +++ b/tests/test_transform.py @@ -0,0 +1,425 @@ +from __future__ import annotations + +import io +import pathlib +from typing import Any, List, Union, TypeVar, Iterable, Optional, cast +from datetime import date, datetime +from typing_extensions import Required, Annotated, TypedDict + +import pytest + +from codex._types import Base64FileInput +from codex._utils import ( + PropertyInfo, + transform as _transform, + parse_datetime, + async_transform as _async_transform, +) +from codex._compat import PYDANTIC_V2 +from codex._models import BaseModel + +_T = TypeVar("_T") + +SAMPLE_FILE_PATH = pathlib.Path(__file__).parent.joinpath("sample_file.txt") + + +async def transform( + data: _T, + expected_type: object, + use_async: bool, +) -> _T: + if use_async: + return await _async_transform(data, expected_type=expected_type) + + return _transform(data, expected_type=expected_type) + + +parametrize = pytest.mark.parametrize("use_async", [False, True], ids=["sync", "async"]) + + +class Foo1(TypedDict): + foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] + + +@parametrize +@pytest.mark.asyncio +async def test_top_level_alias(use_async: bool) -> None: + assert await transform({"foo_bar": "hello"}, expected_type=Foo1, use_async=use_async) == {"fooBar": "hello"} + + +class Foo2(TypedDict): + bar: Bar2 + + +class Bar2(TypedDict): + this_thing: Annotated[int, PropertyInfo(alias="this__thing")] + baz: Annotated[Baz2, PropertyInfo(alias="Baz")] + + +class Baz2(TypedDict): + my_baz: Annotated[str, PropertyInfo(alias="myBaz")] + + +@parametrize +@pytest.mark.asyncio +async def test_recursive_typeddict(use_async: bool) -> None: + assert await transform({"bar": {"this_thing": 1}}, Foo2, use_async) == {"bar": {"this__thing": 1}} + assert await transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2, use_async) == {"bar": {"Baz": {"myBaz": "foo"}}} + + +class Foo3(TypedDict): + things: List[Bar3] + + +class Bar3(TypedDict): + my_field: Annotated[str, PropertyInfo(alias="myField")] + + +@parametrize +@pytest.mark.asyncio +async def test_list_of_typeddict(use_async: bool) -> None: + result = await transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, Foo3, use_async) + assert result == {"things": [{"myField": "foo"}, {"myField": "foo2"}]} + + +class Foo4(TypedDict): + foo: Union[Bar4, Baz4] + + +class Bar4(TypedDict): + foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] + + +class Baz4(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + +@parametrize +@pytest.mark.asyncio +async def test_union_of_typeddict(use_async: bool) -> None: + assert await transform({"foo": {"foo_bar": "bar"}}, Foo4, use_async) == {"foo": {"fooBar": "bar"}} + assert await transform({"foo": {"foo_baz": "baz"}}, Foo4, use_async) == {"foo": {"fooBaz": "baz"}} + assert await transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4, use_async) == { + "foo": {"fooBaz": "baz", "fooBar": "bar"} + } + + +class Foo5(TypedDict): + foo: Annotated[Union[Bar4, List[Baz4]], PropertyInfo(alias="FOO")] + + +class Bar5(TypedDict): + foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] + + +class Baz5(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + +@parametrize +@pytest.mark.asyncio +async def test_union_of_list(use_async: bool) -> None: + assert await transform({"foo": {"foo_bar": "bar"}}, Foo5, use_async) == {"FOO": {"fooBar": "bar"}} + assert await transform( + { + "foo": [ + {"foo_baz": "baz"}, + {"foo_baz": "baz"}, + ] + }, + Foo5, + use_async, + ) == {"FOO": [{"fooBaz": "baz"}, {"fooBaz": "baz"}]} + + +class Foo6(TypedDict): + bar: Annotated[str, PropertyInfo(alias="Bar")] + + +@parametrize +@pytest.mark.asyncio +async def test_includes_unknown_keys(use_async: bool) -> None: + assert await transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6, use_async) == { + "Bar": "bar", + "baz_": {"FOO": 1}, + } + + +class Foo7(TypedDict): + bar: Annotated[List[Bar7], PropertyInfo(alias="bAr")] + foo: Bar7 + + +class Bar7(TypedDict): + foo: str + + +@parametrize +@pytest.mark.asyncio +async def test_ignores_invalid_input(use_async: bool) -> None: + assert await transform({"bar": ""}, Foo7, use_async) == {"bAr": ""} + assert await transform({"foo": ""}, Foo7, use_async) == {"foo": ""} + + +class DatetimeDict(TypedDict, total=False): + foo: Annotated[datetime, PropertyInfo(format="iso8601")] + + bar: Annotated[Optional[datetime], PropertyInfo(format="iso8601")] + + required: Required[Annotated[Optional[datetime], PropertyInfo(format="iso8601")]] + + list_: Required[Annotated[Optional[List[datetime]], PropertyInfo(format="iso8601")]] + + union: Annotated[Union[int, datetime], PropertyInfo(format="iso8601")] + + +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 = "Z" if PYDANTIC_V2 else "+00:00" + 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 +@pytest.mark.asyncio +async def test_optional_iso8601_format(use_async: bool) -> None: + dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") + assert await transform({"bar": dt}, DatetimeDict, use_async) == {"bar": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + + assert await transform({"bar": None}, DatetimeDict, use_async) == {"bar": None} + + +@parametrize +@pytest.mark.asyncio +async def test_required_iso8601_format(use_async: bool) -> None: + dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") + assert await transform({"required": dt}, DatetimeDict, use_async) == { + "required": "2023-02-23T14:16:36.337692+00:00" + } # type: ignore[comparison-overlap] + + assert await transform({"required": None}, DatetimeDict, use_async) == {"required": None} + + +@parametrize +@pytest.mark.asyncio +async def test_union_datetime(use_async: bool) -> None: + dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") + assert await transform({"union": dt}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] + "union": "2023-02-23T14:16:36.337692+00:00" + } + + assert await transform({"union": "foo"}, DatetimeDict, use_async) == {"union": "foo"} + + +@parametrize +@pytest.mark.asyncio +async def test_nested_list_iso6801_format(use_async: bool) -> None: + dt1 = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") + dt2 = parse_datetime("2022-01-15T06:34:23Z") + assert await transform({"list_": [dt1, dt2]}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] + "list_": ["2023-02-23T14:16:36.337692+00:00", "2022-01-15T06:34:23+00:00"] + } + + +@parametrize +@pytest.mark.asyncio +async def test_datetime_custom_format(use_async: bool) -> None: + dt = parse_datetime("2022-01-15T06:34:23Z") + + result = await transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")], use_async) + assert result == "06" # type: ignore[comparison-overlap] + + +class DateDictWithRequiredAlias(TypedDict, total=False): + required_prop: Required[Annotated[date, PropertyInfo(format="iso8601", alias="prop")]] + + +@parametrize +@pytest.mark.asyncio +async def test_datetime_with_alias(use_async: bool) -> None: + assert await transform({"required_prop": None}, DateDictWithRequiredAlias, use_async) == {"prop": None} # type: ignore[comparison-overlap] + assert await transform( + {"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias, use_async + ) == {"prop": "2023-02-23"} # type: ignore[comparison-overlap] + + +class MyModel(BaseModel): + foo: str + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_model_to_dictionary(use_async: bool) -> None: + assert cast(Any, await transform(MyModel(foo="hi!"), Any, use_async)) == {"foo": "hi!"} + assert cast(Any, await transform(MyModel.construct(foo="hi!"), Any, use_async)) == {"foo": "hi!"} + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_empty_model(use_async: bool) -> None: + assert cast(Any, await transform(MyModel.construct(), Any, use_async)) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_unknown_field(use_async: bool) -> None: + assert cast(Any, await transform(MyModel.construct(my_untyped_field=True), Any, use_async)) == { + "my_untyped_field": True + } + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_mismatched_types(use_async: bool) -> None: + model = MyModel.construct(foo=True) + if PYDANTIC_V2: + 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} + + +@parametrize +@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: + 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"}} + + +class ModelNestedObjects(BaseModel): + nested: MyModel + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_nested_objects(use_async: bool) -> None: + model = ModelNestedObjects.construct(nested={"foo": "stainless"}) + assert isinstance(model.nested, MyModel) + assert cast(Any, await transform(model, Any, use_async)) == {"nested": {"foo": "stainless"}} + + +class ModelWithDefaultField(BaseModel): + foo: str + with_none_default: Union[str, None] = None + with_str_default: str = "foo" + + +@parametrize +@pytest.mark.asyncio +async def test_pydantic_default_field(use_async: bool) -> None: + # should be excluded when defaults are used + model = ModelWithDefaultField.construct() + assert model.with_none_default is None + assert model.with_str_default == "foo" + assert cast(Any, await transform(model, Any, use_async)) == {} + + # should be included when the default value is explicitly given + model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo") + assert model.with_none_default is None + assert model.with_str_default == "foo" + assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": None, "with_str_default": "foo"} + + # should be included when a non-default value is explicitly given + model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz") + assert model.with_none_default == "bar" + assert model.with_str_default == "baz" + assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"} + + +class TypedDictIterableUnion(TypedDict): + foo: Annotated[Union[Bar8, Iterable[Baz8]], PropertyInfo(alias="FOO")] + + +class Bar8(TypedDict): + foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] + + +class Baz8(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + +@parametrize +@pytest.mark.asyncio +async def test_iterable_of_dictionaries(use_async: bool) -> None: + assert await transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion, use_async) == { + "FOO": [{"fooBaz": "bar"}] + } + assert cast(Any, await transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion, use_async)) == { + "FOO": [{"fooBaz": "bar"}] + } + + def my_iter() -> Iterable[Baz8]: + yield {"foo_baz": "hello"} + yield {"foo_baz": "world"} + + assert await transform({"foo": my_iter()}, TypedDictIterableUnion, use_async) == { + "FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}] + } + + +class TypedDictIterableUnionStr(TypedDict): + foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")] + + +@parametrize +@pytest.mark.asyncio +async def test_iterable_union_str(use_async: bool) -> None: + assert await transform({"foo": "bar"}, TypedDictIterableUnionStr, use_async) == {"FOO": "bar"} + assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [ + {"fooBaz": "bar"} + ] + + +class TypedDictBase64Input(TypedDict): + foo: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] + + +@parametrize +@pytest.mark.asyncio +async def test_base64_file_input(use_async: bool) -> None: + # strings are left as-is + assert await transform({"foo": "bar"}, TypedDictBase64Input, use_async) == {"foo": "bar"} + + # pathlib.Path is automatically converted to base64 + assert await transform({"foo": SAMPLE_FILE_PATH}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQo=" + } # type: ignore[comparison-overlap] + + # io instances are automatically converted to base64 + assert await transform({"foo": io.StringIO("Hello, world!")}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQ==" + } # type: ignore[comparison-overlap] + assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQ==" + } # type: ignore[comparison-overlap] diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py new file mode 100644 index 00000000..1277e93e --- /dev/null +++ b/tests/test_utils/test_proxy.py @@ -0,0 +1,23 @@ +import operator +from typing import Any +from typing_extensions import override + +from codex._utils import LazyProxy + + +class RecursiveLazyProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + return self + + def __call__(self, *_args: Any, **_kwds: Any) -> Any: + raise RuntimeError("This should never be called!") + + +def test_recursive_proxy() -> None: + proxy = RecursiveLazyProxy() + assert repr(proxy) == "RecursiveLazyProxy" + assert str(proxy) == "RecursiveLazyProxy" + assert dir(proxy) == [] + assert type(proxy).__name__ == "RecursiveLazyProxy" + assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" diff --git a/tests/test_utils/test_typing.py b/tests/test_utils/test_typing.py new file mode 100644 index 00000000..be4ca5e2 --- /dev/null +++ b/tests/test_utils/test_typing.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +from typing import Generic, TypeVar, cast + +from codex._utils import extract_type_var_from_base + +_T = TypeVar("_T") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") + + +class BaseGeneric(Generic[_T]): ... + + +class SubclassGeneric(BaseGeneric[_T]): ... + + +class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): ... + + +class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): ... + + +class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): ... + + +def test_extract_type_var() -> None: + assert ( + extract_type_var_from_base( + BaseGeneric[int], + index=0, + generic_bases=cast("tuple[type, ...]", (BaseGeneric,)), + ) + == int + ) + + +def test_extract_type_var_generic_subclass() -> None: + assert ( + extract_type_var_from_base( + SubclassGeneric[int], + index=0, + generic_bases=cast("tuple[type, ...]", (BaseGeneric,)), + ) + == int + ) + + +def test_extract_type_var_multiple() -> None: + typ = BaseGenericMultipleTypeArgs[int, str, None] + + generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) + assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int + assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str + assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) + + +def test_extract_type_var_generic_subclass_multiple() -> None: + typ = SubclassGenericMultipleTypeArgs[int, str, None] + + generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) + assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int + assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str + assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) + + +def test_extract_type_var_generic_subclass_different_ordering_multiple() -> None: + typ = SubclassDifferentOrderGenericMultipleTypeArgs[int, str, None] + + generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) + assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int + assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str + assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..d56fd573 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +import os +import inspect +import traceback +import contextlib +from typing import Any, TypeVar, Iterator, cast +from datetime import date, datetime +from typing_extensions import Literal, get_args, get_origin, assert_type + +from codex._types import Omit, NoneType +from codex._utils import ( + is_dict, + is_list, + is_list_type, + is_union_type, + extract_type_arg, + is_annotated_type, + is_type_alias_type, +) +from codex._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from codex._models import BaseModel + +BaseModelT = TypeVar("BaseModelT", bound=BaseModel) + + +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: + # 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) + + assert_matches_type( + field_outer_type(field), + field_value, + path=[*path, name], + allow_none=allow_none, + ) + + return True + + +# Note: the `path` argument is only used to improve error messages when `--showlocals` is used +def assert_matches_type( + type_: Any, + value: object, + *, + 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) + + if allow_none and value is None: + return + + if type_ is None or type_ is NoneType: + assert value is None + return + + origin = get_origin(type_) or type_ + + if is_list_type(type_): + return _assert_list_type(type_, value) + + if origin == str: + assert isinstance(value, str) + elif origin == int: + assert isinstance(value, int) + elif origin == bool: + assert isinstance(value, bool) + elif origin == float: + assert isinstance(value, float) + elif origin == bytes: + assert isinstance(value, bytes) + elif origin == datetime: + assert isinstance(value, datetime) + elif origin == date: + assert isinstance(value, date) + elif origin == object: + # nothing to do here, the expected type is unknown + pass + elif origin == Literal: + assert value in get_args(type_) + elif origin == dict: + assert is_dict(value) + + args = get_args(type_) + key_type = args[0] + items_type = args[1] + + for key, item in value.items(): + assert_matches_type(key_type, key, path=[*path, ""]) + assert_matches_type(items_type, item, path=[*path, ""]) + elif is_union_type(type_): + variants = get_args(type_) + + try: + none_index = variants.index(type(None)) + except ValueError: + pass + else: + # special case Optional[T] for better error messages + if len(variants) == 2: + if value is None: + # valid + return + + return assert_matches_type(type_=variants[not none_index], value=value, path=path) + + for i, variant in enumerate(variants): + try: + assert_matches_type(variant, value, path=[*path, f"variant {i}"]) + return + except AssertionError: + traceback.print_exc() + continue + + raise AssertionError("Did not match any variants") + elif issubclass(origin, BaseModel): + assert isinstance(value, type_) + assert assert_matches_model(type_, cast(Any, value), path=path) + elif inspect.isclass(origin) and origin.__name__ == "HttpxBinaryResponseContent": + assert value.__class__.__name__ == "HttpxBinaryResponseContent" + else: + assert None, f"Unhandled field type: {type_}" + + +def _assert_list_type(type_: type[object], value: object) -> None: + assert is_list(value) + + inner_type = get_args(type_)[0] + for entry in value: + assert_type(inner_type, entry) # type: ignore + + +@contextlib.contextmanager +def update_env(**new_env: str | Omit) -> Iterator[None]: + old = os.environ.copy() + + try: + for name, value in new_env.items(): + if isinstance(value, Omit): + os.environ.pop(name, None) + else: + os.environ[name] = value + + yield None + finally: + os.environ.clear() + os.environ.update(old) From a02e655a22c5cea2b579165bf05e3065abb3d722 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 03:32:38 +0000 Subject: [PATCH 002/320] feat(api): update via SDK Studio --- src/codex/_client.py | 93 ++++++---- tests/conftest.py | 20 +-- tests/test_client.py | 399 +++++-------------------------------------- 3 files changed, 106 insertions(+), 406 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index bda9e89c..d94587d7 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -13,6 +13,7 @@ from ._types import ( NOT_GIVEN, Omit, + Headers, Timeout, NotGiven, Transport, @@ -26,7 +27,7 @@ from ._version import __version__ from .resources import health from ._streaming import Stream as Stream, AsyncStream as AsyncStream -from ._exceptions import CodexError, APIStatusError +from ._exceptions import APIStatusError from ._base_client import ( DEFAULT_MAX_RETRIES, SyncAPIClient, @@ -48,9 +49,9 @@ class Codex(SyncAPIClient): with_streaming_response: CodexWithStreamedResponse # client options - bearer_token: str - api_key: str - access_key: str + bearer_token: str | None + api_key: str | None + access_key: str | None def __init__( self, @@ -86,26 +87,14 @@ def __init__( """ if bearer_token is None: bearer_token = os.environ.get("BEARER_TOKEN") - if bearer_token is None: - raise CodexError( - "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable" - ) self.bearer_token = bearer_token if api_key is None: api_key = os.environ.get("AUTHENTICATED_API_KEY") - if api_key is None: - raise CodexError( - "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable" - ) self.api_key = api_key if access_key is None: access_key = os.environ.get("PUBLIC_ACCESS_KEY") - if access_key is None: - raise CodexError( - "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable" - ) self.access_key = access_key if base_url is None: @@ -150,16 +139,22 @@ def auth_headers(self) -> dict[str, str]: @property def _http_bearer(self) -> dict[str, str]: bearer_token = self.bearer_token + if bearer_token is None: + return {} return {"Authorization": f"Bearer {bearer_token}"} @property def _authenticated_api_key(self) -> dict[str, str]: api_key = self.api_key + if api_key is None: + return {} return {"X-API-Key": api_key} @property def _public_access_key(self) -> dict[str, str]: access_key = self.access_key + if access_key is None: + return {} return {"X-Access-Key": access_key} @property @@ -171,6 +166,27 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.bearer_token and headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + + if self.api_key and headers.get("X-API-Key"): + return + if isinstance(custom_headers.get("X-API-Key"), Omit): + return + + if self.access_key and headers.get("X-Access-Key"): + return + if isinstance(custom_headers.get("X-Access-Key"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + ) + def copy( self, *, @@ -269,9 +285,9 @@ class AsyncCodex(AsyncAPIClient): with_streaming_response: AsyncCodexWithStreamedResponse # client options - bearer_token: str - api_key: str - access_key: str + bearer_token: str | None + api_key: str | None + access_key: str | None def __init__( self, @@ -307,26 +323,14 @@ def __init__( """ if bearer_token is None: bearer_token = os.environ.get("BEARER_TOKEN") - if bearer_token is None: - raise CodexError( - "The bearer_token client option must be set either by passing bearer_token to the client or by setting the BEARER_TOKEN environment variable" - ) self.bearer_token = bearer_token if api_key is None: api_key = os.environ.get("AUTHENTICATED_API_KEY") - if api_key is None: - raise CodexError( - "The api_key client option must be set either by passing api_key to the client or by setting the AUTHENTICATED_API_KEY environment variable" - ) self.api_key = api_key if access_key is None: access_key = os.environ.get("PUBLIC_ACCESS_KEY") - if access_key is None: - raise CodexError( - "The access_key client option must be set either by passing access_key to the client or by setting the PUBLIC_ACCESS_KEY environment variable" - ) self.access_key = access_key if base_url is None: @@ -371,16 +375,22 @@ def auth_headers(self) -> dict[str, str]: @property def _http_bearer(self) -> dict[str, str]: bearer_token = self.bearer_token + if bearer_token is None: + return {} return {"Authorization": f"Bearer {bearer_token}"} @property def _authenticated_api_key(self) -> dict[str, str]: api_key = self.api_key + if api_key is None: + return {} return {"X-API-Key": api_key} @property def _public_access_key(self) -> dict[str, str]: access_key = self.access_key + if access_key is None: + return {} return {"X-Access-Key": access_key} @property @@ -392,6 +402,27 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.bearer_token and headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + + if self.api_key and headers.get("X-API-Key"): + return + if isinstance(custom_headers.get("X-API-Key"), Omit): + return + + if self.access_key and headers.get("X-Access-Key"): + return + if isinstance(custom_headers.get("X-Access-Key"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + ) + def copy( self, *, diff --git a/tests/conftest.py b/tests/conftest.py index da139dcc..8823ef7a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,10 +28,6 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -bearer_token = "My Bearer Token" -api_key = "My API Key" -access_key = "My Access Key" - @pytest.fixture(scope="session") def client(request: FixtureRequest) -> Iterator[Codex]: @@ -39,13 +35,7 @@ def client(request: FixtureRequest) -> Iterator[Codex]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=strict, - ) as client: + with Codex(base_url=base_url, _strict_response_validation=strict) as client: yield client @@ -55,11 +45,5 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - async with AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=strict, - ) as client: + async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index d36040f6..91933054 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -30,9 +30,6 @@ from .utils import update_env base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -bearer_token = "My Bearer Token" -api_key = "My API Key" -access_key = "My Access Key" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -54,13 +51,7 @@ def _get_open_connections(client: Codex | AsyncCodex) -> int: class TestCodex: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = Codex(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -86,18 +77,6 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(bearer_token="another My Bearer Token") - assert copied.bearer_token == "another My Bearer Token" - assert self.client.bearer_token == "My Bearer Token" - - copied = self.client.copy(api_key="another My API Key") - assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" - - copied = self.client.copy(access_key="another My Access Key") - assert copied.access_key == "another My Access Key" - assert self.client.access_key == "My Access Key" - def test_copy_default_options(self) -> None: # options that have a default are overridden correctly copied = self.client.copy(max_retries=7) @@ -115,14 +94,7 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, - ) + client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -154,14 +126,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_query={"foo": "bar"}, - ) + client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -284,14 +249,7 @@ def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - timeout=httpx.Timeout(0), - ) + client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -300,14 +258,7 @@ def test_client_timeout_option(self) -> None: 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: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=http_client, - ) + client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -315,14 +266,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=http_client, - ) + client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -330,14 +274,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=http_client, - ) + client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -346,33 +283,16 @@ def test_http_client_timeout_option(self) -> None: async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: - Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=cast(Any, http_client), - ) + Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) def test_default_headers_option(self) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, - ) + client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" client2 = Codex( base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -384,14 +304,7 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_query={"query_param": "bar"}, - ) + client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -590,13 +503,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Codex( - base_url="https://example.com/from_init", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -605,26 +512,15 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): - client = Codex( - bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True - ) + client = Codex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ + Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), Codex( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ), - Codex( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -644,18 +540,9 @@ def test_base_url_trailing_slash(self, client: Codex) -> None: @pytest.mark.parametrize( "client", [ + Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), Codex( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ), - Codex( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -675,18 +562,9 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None: @pytest.mark.parametrize( "client", [ + Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), Codex( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ), - Codex( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -704,13 +582,7 @@ def test_absolute_request_url(self, client: Codex) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = Codex(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -721,13 +593,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = Codex(base_url=base_url, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -748,14 +614,7 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - max_retries=cast(Any, None), - ) + Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -764,24 +623,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + strict_client = Codex(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=False, - ) + client = Codex(base_url=base_url, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -809,13 +656,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Codex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = Codex(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -941,13 +782,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: class TestAsyncCodex: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -975,18 +810,6 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(bearer_token="another My Bearer Token") - assert copied.bearer_token == "another My Bearer Token" - assert self.client.bearer_token == "My Bearer Token" - - copied = self.client.copy(api_key="another My API Key") - assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" - - copied = self.client.copy(access_key="another My Access Key") - assert copied.access_key == "another My Access Key" - assert self.client.access_key == "My Access Key" - def test_copy_default_options(self) -> None: # options that have a default are overridden correctly copied = self.client.copy(max_retries=7) @@ -1004,14 +827,7 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -1043,14 +859,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_query={"foo": "bar"}, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -1173,14 +982,7 @@ async def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) async def test_client_timeout_option(self) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - timeout=httpx.Timeout(0), - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1189,14 +991,7 @@ async def test_client_timeout_option(self) -> None: 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: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=http_client, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1204,14 +999,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=http_client, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1219,14 +1007,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=http_client, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1235,33 +1016,16 @@ async def test_http_client_timeout_option(self) -> None: def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: - AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - http_client=cast(Any, http_client), - ) + AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) def test_default_headers_option(self) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" client2 = AsyncCodex( base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1273,14 +1037,7 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - default_query={"query_param": "bar"}, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -1479,13 +1236,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = AsyncCodex( - base_url="https://example.com/from_init", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -1494,26 +1245,15 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): - client = AsyncCodex( - bearer_token=bearer_token, api_key=api_key, access_key=access_key, _strict_response_validation=True - ) + client = AsyncCodex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ + AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), AsyncCodex( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ), - AsyncCodex( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1533,18 +1273,9 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: @pytest.mark.parametrize( "client", [ + AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), AsyncCodex( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ), - AsyncCodex( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1564,18 +1295,9 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: @pytest.mark.parametrize( "client", [ + AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), AsyncCodex( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ), - AsyncCodex( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1593,13 +1315,7 @@ def test_absolute_request_url(self, client: AsyncCodex) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1611,13 +1327,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1639,14 +1349,7 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - max_retries=cast(Any, None), - ) + AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -1656,24 +1359,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=False, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1702,13 +1393,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncCodex( - base_url=base_url, - bearer_token=bearer_token, - api_key=api_key, - access_key=access_key, - _strict_response_validation=True, - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) From 6e31719f0e871ce1f0dac6fad312b6ca3c18c5e7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:02:07 +0000 Subject: [PATCH 003/320] feat(api): update via SDK Studio --- README.md | 10 ++++- src/codex/__init__.py | 14 +++++- src/codex/_client.py | 94 +++++++++++++++++++++++++++++++++++------ src/codex/pagination.py | 78 ++++++++++++++++++++++++++++++++++ tests/test_client.py | 16 +++++++ 5 files changed, 196 insertions(+), 16 deletions(-) create mode 100644 src/codex/pagination.py diff --git a/README.md b/README.md index e4bf463c..301f9e26 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,10 @@ The full API of this library can be found in [api.md](api.md). ```python from codex import Codex -client = Codex() +client = Codex( + # or 'production' | 'local'; defaults to "production". + environment="staging", +) project_return_schema = client.projects.create( config={}, @@ -52,7 +55,10 @@ Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call import asyncio from codex import AsyncCodex -client = AsyncCodex() +client = AsyncCodex( + # or 'production' | 'local'; defaults to "production". + environment="staging", +) async def main() -> None: diff --git a/src/codex/__init__.py b/src/codex/__init__.py index bdcdb71a..d6dffe2c 100644 --- a/src/codex/__init__.py +++ b/src/codex/__init__.py @@ -3,7 +3,18 @@ from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path -from ._client import Codex, Client, Stream, Timeout, Transport, AsyncCodex, AsyncClient, AsyncStream, RequestOptions +from ._client import ( + ENVIRONMENTS, + Codex, + Client, + Stream, + Timeout, + Transport, + AsyncCodex, + AsyncClient, + AsyncStream, + RequestOptions, +) from ._models import BaseModel from ._version import __title__, __version__ from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse @@ -59,6 +70,7 @@ "AsyncStream", "Codex", "AsyncCodex", + "ENVIRONMENTS", "file_from_path", "BaseModel", "DEFAULT_TIMEOUT", diff --git a/src/codex/_client.py b/src/codex/_client.py index d94587d7..e12ce243 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -3,8 +3,8 @@ from __future__ import annotations import os -from typing import Any, Union, Mapping -from typing_extensions import Self, override +from typing import Any, Dict, Union, Mapping, cast +from typing_extensions import Self, Literal, override import httpx @@ -37,7 +37,23 @@ from .resources.projects import projects from .resources.organizations import organizations -__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Codex", "AsyncCodex", "Client", "AsyncClient"] +__all__ = [ + "ENVIRONMENTS", + "Timeout", + "Transport", + "ProxiesTypes", + "RequestOptions", + "Codex", + "AsyncCodex", + "Client", + "AsyncClient", +] + +ENVIRONMENTS: Dict[str, str] = { + "production": "https://api-alpha-o3gxj3oajfu.cleanlab.ai", + "staging": "https://api-alpha-staging-o3gxj3oajfu.cleanlab.ai", + "local": "http://localhost:8080", +} class Codex(SyncAPIClient): @@ -53,13 +69,16 @@ class Codex(SyncAPIClient): api_key: str | None access_key: str | None + _environment: Literal["production", "staging", "local"] | NotGiven + def __init__( self, *, bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, - base_url: str | httpx.URL | None = None, + environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, + base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -97,10 +116,31 @@ def __init__( access_key = os.environ.get("PUBLIC_ACCESS_KEY") self.access_key = access_key - if base_url is None: - base_url = os.environ.get("CODEX_BASE_URL") - if base_url is None: - base_url = f"https://localhost:8080/test-api" + self._environment = environment + + base_url_env = os.environ.get("CODEX_BASE_URL") + if is_given(base_url) and base_url is not None: + # cast required because mypy doesn't understand the type narrowing + base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] + elif is_given(environment): + if base_url_env and base_url is not None: + raise ValueError( + "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + ) + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + elif base_url_env is not None: + base_url = base_url_env + else: + self._environment = environment = "production" + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc super().__init__( version=__version__, @@ -193,6 +233,7 @@ def copy( bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, + environment: Literal["production", "staging", "local"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.Client | None = None, @@ -230,6 +271,7 @@ def copy( api_key=api_key or self.api_key, access_key=access_key or self.access_key, base_url=base_url or self.base_url, + environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, @@ -289,13 +331,16 @@ class AsyncCodex(AsyncAPIClient): api_key: str | None access_key: str | None + _environment: Literal["production", "staging", "local"] | NotGiven + def __init__( self, *, bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, - base_url: str | httpx.URL | None = None, + environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, + base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -333,10 +378,31 @@ def __init__( access_key = os.environ.get("PUBLIC_ACCESS_KEY") self.access_key = access_key - if base_url is None: - base_url = os.environ.get("CODEX_BASE_URL") - if base_url is None: - base_url = f"https://localhost:8080/test-api" + self._environment = environment + + base_url_env = os.environ.get("CODEX_BASE_URL") + if is_given(base_url) and base_url is not None: + # cast required because mypy doesn't understand the type narrowing + base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] + elif is_given(environment): + if base_url_env and base_url is not None: + raise ValueError( + "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + ) + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + elif base_url_env is not None: + base_url = base_url_env + else: + self._environment = environment = "production" + + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc super().__init__( version=__version__, @@ -429,6 +495,7 @@ def copy( bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, + environment: Literal["production", "staging", "local"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.AsyncClient | None = None, @@ -466,6 +533,7 @@ def copy( api_key=api_key or self.api_key, access_key=access_key or self.access_key, base_url=base_url or self.base_url, + environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, diff --git a/src/codex/pagination.py b/src/codex/pagination.py new file mode 100644 index 00000000..963a4310 --- /dev/null +++ b/src/codex/pagination.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast +from typing_extensions import override + +from httpx import Response + +from ._utils import is_mapping +from ._models import BaseModel +from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage + +__all__ = ["SyncMyOffsetPage", "AsyncMyOffsetPage"] + +_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) + +_T = TypeVar("_T") + + +class SyncMyOffsetPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + items: List[_T] + + @override + def _get_page_items(self) -> List[_T]: + items = self.items + if not items: + return [] + return items + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + return PageInfo(params={"offset": current_count}) + + @classmethod + def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 + return cls.construct( + None, + **{ + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), + }, + ) + + +class AsyncMyOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + items: List[_T] + + @override + def _get_page_items(self) -> List[_T]: + items = self.items + if not items: + return [] + return items + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + return PageInfo(params={"offset": current_count}) + + @classmethod + def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 + return cls.construct( + None, + **{ + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), + }, + ) diff --git a/tests/test_client.py b/tests/test_client.py index 91933054..4e76d238 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -515,6 +515,14 @@ def test_base_url_env(self) -> None: client = Codex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" + # explicit environment arg requires explicitness + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + with pytest.raises(ValueError, match=r"you must pass base_url=None"): + Codex(_strict_response_validation=True, environment="production") + + client = Codex(base_url=None, _strict_response_validation=True, environment="production") + assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") + @pytest.mark.parametrize( "client", [ @@ -1248,6 +1256,14 @@ def test_base_url_env(self) -> None: client = AsyncCodex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" + # explicit environment arg requires explicitness + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + with pytest.raises(ValueError, match=r"you must pass base_url=None"): + AsyncCodex(_strict_response_validation=True, environment="production") + + client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production") + assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") + @pytest.mark.parametrize( "client", [ From e9508424ff814bda845dc3fd0aa38ca97f34e879 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:02:33 +0000 Subject: [PATCH 004/320] feat(api): update via SDK Studio --- LICENSE | 2 +- README.md | 48 ++--- SECURITY.md | 6 +- pyproject.toml | 4 +- src/codex/__init__.py | 12 +- src/codex/_client.py | 56 +++--- src/codex/_exceptions.py | 4 +- src/codex/_resource.py | 10 +- src/codex/_response.py | 4 +- src/codex/_streaming.py | 6 +- src/codex/_utils/_logs.py | 2 +- .../organizations/test_billing.py | 34 ++-- .../projects/test_access_keys.py | 82 ++++----- .../api_resources/projects/test_knowledge.py | 110 ++++++------ tests/api_resources/test_health.py | 38 ++-- tests/api_resources/test_organizations.py | 18 +- tests/api_resources/test_projects.py | 82 ++++----- .../users/myself/test_api_key.py | 14 +- .../users/myself/test_organizations.py | 14 +- tests/api_resources/users/test_myself.py | 14 +- tests/conftest.py | 10 +- tests/test_client.py | 164 ++++++++++-------- tests/test_response.py | 26 +-- tests/test_streaming.py | 34 ++-- 24 files changed, 405 insertions(+), 389 deletions(-) diff --git a/LICENSE b/LICENSE index f00d3a6a..468d5b60 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Codex + Copyright 2025 Cleanlab Codex 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 301f9e26..1e28f2a7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Codex Python API library +# Cleanlab Codex Python API library [![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/) -The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+ +The Cleanlab Codex Python library provides convenient access to the Cleanlab Codex REST API from any Python 3.8+ 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). @@ -10,7 +10,7 @@ It is generated with [Stainless](https://www.stainlessapi.com/). ## Documentation -The REST API documentation can be found on [docs.codex.com](https://docs.codex.com). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [help.cleanlab.ai](https://help.cleanlab.ai). The full API of this library can be found in [api.md](api.md). ## Installation @@ -27,9 +27,9 @@ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git The full API of this library can be found in [api.md](api.md). ```python -from codex import Codex +from codex import CleanlabCodex -client = Codex( +client = CleanlabCodex( # or 'production' | 'local'; defaults to "production". environment="staging", ) @@ -49,13 +49,13 @@ so that your Bearer Token is not stored in source control. ## Async usage -Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call: +Simply import `AsyncCleanlabCodex` instead of `CleanlabCodex` and use `await` with each API call: ```python import asyncio -from codex import AsyncCodex +from codex import AsyncCleanlabCodex -client = AsyncCodex( +client = AsyncCleanlabCodex( # or 'production' | 'local'; defaults to "production". environment="staging", ) @@ -95,9 +95,9 @@ All errors inherit from `codex.APIError`. ```python import codex -from codex import Codex +from codex import CleanlabCodex -client = Codex() +client = CleanlabCodex() try: client.projects.create( @@ -138,10 +138,10 @@ Connection errors (for example, due to a network connectivity problem), 408 Requ You can use the `max_retries` option to configure or disable retry settings: ```python -from codex import Codex +from codex import CleanlabCodex # Configure the default for all requests: -client = Codex( +client = CleanlabCodex( # default is 2 max_retries=0, ) @@ -160,16 +160,16 @@ By default requests time out after 1 minute. You can configure this with a `time which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: ```python -from codex import Codex +from codex import CleanlabCodex # Configure the default for all requests: -client = Codex( +client = CleanlabCodex( # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: -client = Codex( +client = CleanlabCodex( timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) @@ -191,10 +191,10 @@ 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 `CODEX_LOG` to `info`. +You can enable logging by setting the environment variable `CLEANLAB_CODEX_LOG` to `info`. ```shell -$ export CODEX_LOG=info +$ export CLEANLAB_CODEX_LOG=info ``` Or to `debug` for more verbose logging. @@ -216,9 +216,9 @@ if response.my_field is None: The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., ```py -from codex import Codex +from codex import CleanlabCodex -client = Codex() +client = CleanlabCodex() response = client.projects.with_raw_response.create( config={}, name="name", @@ -298,10 +298,10 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c ```python import httpx -from codex import Codex, DefaultHttpxClient +from codex import CleanlabCodex, DefaultHttpxClient -client = Codex( - # Or use the `CODEX_BASE_URL` env var +client = CleanlabCodex( + # Or use the `CLEANLAB_CODEX_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( proxy="http://my.test.proxy.example.com", @@ -321,9 +321,9 @@ 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 codex import Codex +from codex import CleanlabCodex -with Codex() as client: +with CleanlabCodex() as client: # make requests here ... diff --git a/SECURITY.md b/SECURITY.md index cf89cd01..c229abd4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Codex please follow the respective company's security reporting guidelines. +or products provided by Cleanlab Codex please follow the respective company's security reporting guidelines. -### Codex Terms and Policies +### Cleanlab Codex Terms and Policies -Please contact dev-feedback@codex.com for any questions or concerns regarding security of our services. +Please contact support@cleanlab.ai for any questions or concerns regarding security of our services. --- diff --git a/pyproject.toml b/pyproject.toml index 5116f227..86518e14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "codex" version = "0.0.1-alpha.0" -description = "The official Python library for the codex API" +description = "The official Python library for the Cleanlab Codex API" dynamic = ["readme"] license = "Apache-2.0" authors = [ -{ name = "Codex", email = "dev-feedback@codex.com" }, +{ name = "Cleanlab Codex", email = "support@cleanlab.ai" }, ] dependencies = [ "httpx>=0.23.0, <1", diff --git a/src/codex/__init__.py b/src/codex/__init__.py index d6dffe2c..62559f31 100644 --- a/src/codex/__init__.py +++ b/src/codex/__init__.py @@ -5,15 +5,15 @@ from ._utils import file_from_path from ._client import ( ENVIRONMENTS, - Codex, Client, Stream, Timeout, Transport, - AsyncCodex, AsyncClient, AsyncStream, + CleanlabCodex, RequestOptions, + AsyncCleanlabCodex, ) from ._models import BaseModel from ._version import __title__, __version__ @@ -21,7 +21,6 @@ from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS from ._exceptions import ( APIError, - CodexError, ConflictError, NotFoundError, APIStatusError, @@ -29,6 +28,7 @@ APITimeoutError, BadRequestError, APIConnectionError, + CleanlabCodexError, AuthenticationError, InternalServerError, PermissionDeniedError, @@ -48,7 +48,7 @@ "NotGiven", "NOT_GIVEN", "Omit", - "CodexError", + "CleanlabCodexError", "APIError", "APIStatusError", "APITimeoutError", @@ -68,8 +68,8 @@ "AsyncClient", "Stream", "AsyncStream", - "Codex", - "AsyncCodex", + "CleanlabCodex", + "AsyncCleanlabCodex", "ENVIRONMENTS", "file_from_path", "BaseModel", diff --git a/src/codex/_client.py b/src/codex/_client.py index e12ce243..3a0f8657 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -43,8 +43,8 @@ "Transport", "ProxiesTypes", "RequestOptions", - "Codex", - "AsyncCodex", + "CleanlabCodex", + "AsyncCleanlabCodex", "Client", "AsyncClient", ] @@ -56,13 +56,13 @@ } -class Codex(SyncAPIClient): +class CleanlabCodex(SyncAPIClient): health: health.HealthResource organizations: organizations.OrganizationsResource users: users.UsersResource projects: projects.ProjectsResource - with_raw_response: CodexWithRawResponse - with_streaming_response: CodexWithStreamedResponse + with_raw_response: CleanlabCodexWithRawResponse + with_streaming_response: CleanlabCodexWithStreamedResponse # client options bearer_token: str | None @@ -97,7 +97,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous codex client instance. + """Construct a new synchronous Cleanlab Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `bearer_token` from `BEARER_TOKEN` @@ -118,14 +118,14 @@ def __init__( self._environment = environment - base_url_env = os.environ.get("CODEX_BASE_URL") + base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL") if is_given(base_url) and base_url is not None: # cast required because mypy doesn't understand the type narrowing base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] elif is_given(environment): if base_url_env and base_url is not None: raise ValueError( - "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", ) try: @@ -157,8 +157,8 @@ def __init__( self.organizations = organizations.OrganizationsResource(self) self.users = users.UsersResource(self) self.projects = projects.ProjectsResource(self) - self.with_raw_response = CodexWithRawResponse(self) - self.with_streaming_response = CodexWithStreamedResponse(self) + self.with_raw_response = CleanlabCodexWithRawResponse(self) + self.with_streaming_response = CleanlabCodexWithStreamedResponse(self) @property @override @@ -318,13 +318,13 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class AsyncCodex(AsyncAPIClient): +class AsyncCleanlabCodex(AsyncAPIClient): health: health.AsyncHealthResource organizations: organizations.AsyncOrganizationsResource users: users.AsyncUsersResource projects: projects.AsyncProjectsResource - with_raw_response: AsyncCodexWithRawResponse - with_streaming_response: AsyncCodexWithStreamedResponse + with_raw_response: AsyncCleanlabCodexWithRawResponse + with_streaming_response: AsyncCleanlabCodexWithStreamedResponse # client options bearer_token: str | None @@ -359,7 +359,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async codex client instance. + """Construct a new async Cleanlab Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `bearer_token` from `BEARER_TOKEN` @@ -380,14 +380,14 @@ def __init__( self._environment = environment - base_url_env = os.environ.get("CODEX_BASE_URL") + base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL") if is_given(base_url) and base_url is not None: # cast required because mypy doesn't understand the type narrowing base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] elif is_given(environment): if base_url_env and base_url is not None: raise ValueError( - "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", ) try: @@ -419,8 +419,8 @@ def __init__( self.organizations = organizations.AsyncOrganizationsResource(self) self.users = users.AsyncUsersResource(self) self.projects = projects.AsyncProjectsResource(self) - self.with_raw_response = AsyncCodexWithRawResponse(self) - self.with_streaming_response = AsyncCodexWithStreamedResponse(self) + self.with_raw_response = AsyncCleanlabCodexWithRawResponse(self) + self.with_streaming_response = AsyncCleanlabCodexWithStreamedResponse(self) @property @override @@ -580,38 +580,38 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class CodexWithRawResponse: - def __init__(self, client: Codex) -> None: +class CleanlabCodexWithRawResponse: + def __init__(self, client: CleanlabCodex) -> None: self.health = health.HealthResourceWithRawResponse(client.health) self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations) self.users = users.UsersResourceWithRawResponse(client.users) self.projects = projects.ProjectsResourceWithRawResponse(client.projects) -class AsyncCodexWithRawResponse: - def __init__(self, client: AsyncCodex) -> None: +class AsyncCleanlabCodexWithRawResponse: + def __init__(self, client: AsyncCleanlabCodex) -> None: self.health = health.AsyncHealthResourceWithRawResponse(client.health) self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations) self.users = users.AsyncUsersResourceWithRawResponse(client.users) self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) -class CodexWithStreamedResponse: - def __init__(self, client: Codex) -> None: +class CleanlabCodexWithStreamedResponse: + def __init__(self, client: CleanlabCodex) -> None: self.health = health.HealthResourceWithStreamingResponse(client.health) self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.UsersResourceWithStreamingResponse(client.users) self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) -class AsyncCodexWithStreamedResponse: - def __init__(self, client: AsyncCodex) -> None: +class AsyncCleanlabCodexWithStreamedResponse: + def __init__(self, client: AsyncCleanlabCodex) -> None: self.health = health.AsyncHealthResourceWithStreamingResponse(client.health) self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.AsyncUsersResourceWithStreamingResponse(client.users) self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) -Client = Codex +Client = CleanlabCodex -AsyncClient = AsyncCodex +AsyncClient = AsyncCleanlabCodex diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py index 90164ee4..c416e8d6 100644 --- a/src/codex/_exceptions.py +++ b/src/codex/_exceptions.py @@ -18,11 +18,11 @@ ] -class CodexError(Exception): +class CleanlabCodexError(Exception): pass -class APIError(CodexError): +class APIError(CleanlabCodexError): message: str request: httpx.Request diff --git a/src/codex/_resource.py b/src/codex/_resource.py index 7ba43eeb..bfbd7eef 100644 --- a/src/codex/_resource.py +++ b/src/codex/_resource.py @@ -8,13 +8,13 @@ import anyio if TYPE_CHECKING: - from ._client import Codex, AsyncCodex + from ._client import CleanlabCodex, AsyncCleanlabCodex class SyncAPIResource: - _client: Codex + _client: CleanlabCodex - def __init__(self, client: Codex) -> None: + def __init__(self, client: CleanlabCodex) -> None: self._client = client self._get = client.get self._post = client.post @@ -28,9 +28,9 @@ def _sleep(self, seconds: float) -> None: class AsyncAPIResource: - _client: AsyncCodex + _client: AsyncCleanlabCodex - def __init__(self, client: AsyncCodex) -> None: + def __init__(self, client: AsyncCleanlabCodex) -> None: self._client = client self._get = client.get self._post = client.post diff --git a/src/codex/_response.py b/src/codex/_response.py index b63926f4..3cbf40e5 100644 --- a/src/codex/_response.py +++ b/src/codex/_response.py @@ -29,7 +29,7 @@ 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 -from ._exceptions import CodexError, APIResponseValidationError +from ._exceptions import CleanlabCodexError, APIResponseValidationError if TYPE_CHECKING: from ._models import FinalRequestOptions @@ -554,7 +554,7 @@ def __init__(self) -> None: ) -class StreamAlreadyConsumed(CodexError): +class StreamAlreadyConsumed(CleanlabCodexError): """ Attempted to read or stream content, but the content has already been streamed. diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py index 3af102ce..820839f9 100644 --- a/src/codex/_streaming.py +++ b/src/codex/_streaming.py @@ -12,7 +12,7 @@ from ._utils import extract_type_var_from_base if TYPE_CHECKING: - from ._client import Codex, AsyncCodex + from ._client import CleanlabCodex, AsyncCleanlabCodex _T = TypeVar("_T") @@ -30,7 +30,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: Codex, + client: CleanlabCodex, ) -> None: self.response = response self._cast_to = cast_to @@ -93,7 +93,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: AsyncCodex, + client: AsyncCleanlabCodex, ) -> None: self.response = response self._cast_to = cast_to diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py index 80932c4f..23658f29 100644 --- a/src/codex/_utils/_logs.py +++ b/src/codex/_utils/_logs.py @@ -14,7 +14,7 @@ def _basic_config() -> None: def setup_logging() -> None: - env = os.environ.get("CODEX_LOG") + env = os.environ.get("CLEANLAB_CODEX_LOG") if env == "debug": _basic_config() logger.setLevel(logging.DEBUG) diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py index ccb7dc8a..c2dbdedb 100644 --- a/tests/api_resources/organizations/test_billing.py +++ b/tests/api_resources/organizations/test_billing.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from tests.utils import assert_matches_type from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema @@ -18,14 +18,14 @@ class TestBilling: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_invoices(self, client: Codex) -> None: + def test_method_invoices(self, client: CleanlabCodex) -> None: billing = client.organizations.billing.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - def test_raw_response_invoices(self, client: Codex) -> None: + def test_raw_response_invoices(self, client: CleanlabCodex) -> None: response = client.organizations.billing.with_raw_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -36,7 +36,7 @@ def test_raw_response_invoices(self, client: Codex) -> None: assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - def test_streaming_response_invoices(self, client: Codex) -> None: + def test_streaming_response_invoices(self, client: CleanlabCodex) -> None: with client.organizations.billing.with_streaming_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -49,21 +49,21 @@ def test_streaming_response_invoices(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_invoices(self, client: Codex) -> None: + def test_path_params_invoices(self, client: CleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.billing.with_raw_response.invoices( "", ) @parametrize - def test_method_usage(self, client: Codex) -> None: + def test_method_usage(self, client: CleanlabCodex) -> None: billing = client.organizations.billing.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - def test_raw_response_usage(self, client: Codex) -> None: + def test_raw_response_usage(self, client: CleanlabCodex) -> None: response = client.organizations.billing.with_raw_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -74,7 +74,7 @@ def test_raw_response_usage(self, client: Codex) -> None: assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - def test_streaming_response_usage(self, client: Codex) -> None: + def test_streaming_response_usage(self, client: CleanlabCodex) -> None: with client.organizations.billing.with_streaming_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -87,7 +87,7 @@ def test_streaming_response_usage(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_usage(self, client: Codex) -> None: + def test_path_params_usage(self, client: CleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.billing.with_raw_response.usage( "", @@ -98,14 +98,14 @@ class TestAsyncBilling: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_invoices(self, async_client: AsyncCodex) -> None: + async def test_method_invoices(self, async_client: AsyncCleanlabCodex) -> None: billing = await async_client.organizations.billing.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: + async def test_raw_response_invoices(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.organizations.billing.with_raw_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -116,7 +116,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_invoices(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.organizations.billing.with_streaming_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -129,21 +129,21 @@ async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_invoices(self, async_client: AsyncCodex) -> None: + async def test_path_params_invoices(self, async_client: AsyncCleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.billing.with_raw_response.invoices( "", ) @parametrize - async def test_method_usage(self, async_client: AsyncCodex) -> None: + async def test_method_usage(self, async_client: AsyncCleanlabCodex) -> None: billing = await async_client.organizations.billing.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: + async def test_raw_response_usage(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.organizations.billing.with_raw_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -154,7 +154,7 @@ async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_usage(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.organizations.billing.with_streaming_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -167,7 +167,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_usage(self, async_client: AsyncCodex) -> None: + async def test_path_params_usage(self, async_client: AsyncCleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.billing.with_raw_response.usage( "", diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index 4fa1636f..6828a32a 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from tests.utils import assert_matches_type from codex._utils import parse_datetime from codex.types.projects import ( @@ -22,7 +22,7 @@ class TestAccessKeys: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: Codex) -> None: + def test_method_create(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.create( project_id=0, name="name", @@ -30,7 +30,7 @@ def test_method_create(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: Codex) -> None: + def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.create( project_id=0, name="name", @@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_create(self, client: Codex) -> None: + def test_raw_response_create(self, client: CleanlabCodex) -> None: response = client.projects.access_keys.with_raw_response.create( project_id=0, name="name", @@ -52,7 +52,7 @@ def test_raw_response_create(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_create(self, client: Codex) -> None: + def test_streaming_response_create(self, client: CleanlabCodex) -> None: with client.projects.access_keys.with_streaming_response.create( project_id=0, name="name", @@ -66,7 +66,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: Codex) -> None: + def test_method_retrieve(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.retrieve( access_key_id=0, project_id=0, @@ -74,7 +74,7 @@ def test_method_retrieve(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Codex) -> None: + def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: response = client.projects.access_keys.with_raw_response.retrieve( access_key_id=0, project_id=0, @@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Codex) -> None: + def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: with client.projects.access_keys.with_streaming_response.retrieve( access_key_id=0, project_id=0, @@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_update(self, client: Codex) -> None: + def test_method_update(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -109,7 +109,7 @@ def test_method_update(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: Codex) -> None: + def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -120,7 +120,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_update(self, client: Codex) -> None: + def test_raw_response_update(self, client: CleanlabCodex) -> None: response = client.projects.access_keys.with_raw_response.update( access_key_id=0, project_id=0, @@ -133,7 +133,7 @@ def test_raw_response_update(self, client: Codex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_update(self, client: Codex) -> None: + def test_streaming_response_update(self, client: CleanlabCodex) -> None: with client.projects.access_keys.with_streaming_response.update( access_key_id=0, project_id=0, @@ -148,14 +148,14 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_list(self, client: Codex) -> None: + def test_method_list(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.list( 0, ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - def test_raw_response_list(self, client: Codex) -> None: + def test_raw_response_list(self, client: CleanlabCodex) -> None: response = client.projects.access_keys.with_raw_response.list( 0, ) @@ -166,7 +166,7 @@ def test_raw_response_list(self, client: Codex) -> None: assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Codex) -> None: + def test_streaming_response_list(self, client: CleanlabCodex) -> None: with client.projects.access_keys.with_streaming_response.list( 0, ) as response: @@ -179,7 +179,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: Codex) -> None: + def test_method_delete(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.delete( access_key_id=0, project_id=0, @@ -187,7 +187,7 @@ def test_method_delete(self, client: Codex) -> None: assert access_key is None @parametrize - def test_raw_response_delete(self, client: Codex) -> None: + def test_raw_response_delete(self, client: CleanlabCodex) -> None: response = client.projects.access_keys.with_raw_response.delete( access_key_id=0, project_id=0, @@ -199,7 +199,7 @@ def test_raw_response_delete(self, client: Codex) -> None: assert access_key is None @parametrize - def test_streaming_response_delete(self, client: Codex) -> None: + def test_streaming_response_delete(self, client: CleanlabCodex) -> None: with client.projects.access_keys.with_streaming_response.delete( access_key_id=0, project_id=0, @@ -213,7 +213,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_revoke(self, client: Codex) -> None: + def test_method_revoke(self, client: CleanlabCodex) -> None: access_key = client.projects.access_keys.revoke( access_key_id=0, project_id=0, @@ -221,7 +221,7 @@ def test_method_revoke(self, client: Codex) -> None: assert access_key is None @parametrize - def test_raw_response_revoke(self, client: Codex) -> None: + def test_raw_response_revoke(self, client: CleanlabCodex) -> None: response = client.projects.access_keys.with_raw_response.revoke( access_key_id=0, project_id=0, @@ -233,7 +233,7 @@ def test_raw_response_revoke(self, client: Codex) -> None: assert access_key is None @parametrize - def test_streaming_response_revoke(self, client: Codex) -> None: + def test_streaming_response_revoke(self, client: CleanlabCodex) -> None: with client.projects.access_keys.with_streaming_response.revoke( access_key_id=0, project_id=0, @@ -251,7 +251,7 @@ class TestAsyncAccessKeys: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCodex) -> None: + async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.create( project_id=0, name="name", @@ -259,7 +259,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.create( project_id=0, name="name", @@ -269,7 +269,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.create( project_id=0, name="name", @@ -281,7 +281,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.create( project_id=0, name="name", @@ -295,7 +295,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.retrieve( access_key_id=0, project_id=0, @@ -303,7 +303,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve( access_key_id=0, project_id=0, @@ -315,7 +315,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve( access_key_id=0, project_id=0, @@ -329,7 +329,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True @parametrize - async def test_method_update(self, async_client: AsyncCodex) -> None: + async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -338,7 +338,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -349,7 +349,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCodex) -> None: + async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.update( access_key_id=0, project_id=0, @@ -362,7 +362,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.update( access_key_id=0, project_id=0, @@ -377,14 +377,14 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_method_list(self, async_client: AsyncCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.list( 0, ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.list( 0, ) @@ -395,7 +395,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.list( 0, ) as response: @@ -408,7 +408,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCodex) -> None: + async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.delete( access_key_id=0, project_id=0, @@ -416,7 +416,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: assert access_key is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.delete( access_key_id=0, project_id=0, @@ -428,7 +428,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: assert access_key is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.delete( access_key_id=0, project_id=0, @@ -442,7 +442,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_method_revoke(self, async_client: AsyncCodex) -> None: + async def test_method_revoke(self, async_client: AsyncCleanlabCodex) -> None: access_key = await async_client.projects.access_keys.revoke( access_key_id=0, project_id=0, @@ -450,7 +450,7 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None: assert access_key is None @parametrize - async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: + async def test_raw_response_revoke(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.revoke( access_key_id=0, project_id=0, @@ -462,7 +462,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: assert access_key is None @parametrize - async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_revoke(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.revoke( access_key_id=0, project_id=0, diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py index 9052b62d..e74a0bcc 100644 --- a/tests/api_resources/projects/test_knowledge.py +++ b/tests/api_resources/projects/test_knowledge.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from tests.utils import assert_matches_type from codex.types.projects import ( Entry, @@ -21,7 +21,7 @@ class TestKnowledge: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: Codex) -> None: + def test_method_create(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.create( project_id=0, question="question", @@ -29,7 +29,7 @@ def test_method_create(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: Codex) -> None: + def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.create( project_id=0, question="question", @@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_create(self, client: Codex) -> None: + def test_raw_response_create(self, client: CleanlabCodex) -> None: response = client.projects.knowledge.with_raw_response.create( project_id=0, question="question", @@ -50,7 +50,7 @@ def test_raw_response_create(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_create(self, client: Codex) -> None: + def test_streaming_response_create(self, client: CleanlabCodex) -> None: with client.projects.knowledge.with_streaming_response.create( project_id=0, question="question", @@ -64,7 +64,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: Codex) -> None: + def test_method_retrieve(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -72,7 +72,7 @@ def test_method_retrieve(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Codex) -> None: + def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: response = client.projects.knowledge.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -84,7 +84,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Codex) -> None: + def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: with client.projects.knowledge.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -98,7 +98,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_retrieve(self, client: Codex) -> None: + def test_path_params_retrieve(self, client: CleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.retrieve( entry_id="", @@ -106,7 +106,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: ) @parametrize - def test_method_update(self, client: Codex) -> None: + def test_method_update(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -114,7 +114,7 @@ def test_method_update(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: Codex) -> None: + def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -124,7 +124,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_update(self, client: Codex) -> None: + def test_raw_response_update(self, client: CleanlabCodex) -> None: response = client.projects.knowledge.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -136,7 +136,7 @@ def test_raw_response_update(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_update(self, client: Codex) -> None: + def test_streaming_response_update(self, client: CleanlabCodex) -> None: with client.projects.knowledge.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -150,7 +150,7 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_update(self, client: Codex) -> None: + def test_path_params_update(self, client: CleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.update( entry_id="", @@ -158,14 +158,14 @@ def test_path_params_update(self, client: Codex) -> None: ) @parametrize - def test_method_list(self, client: Codex) -> None: + def test_method_list(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.list( project_id=0, ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_method_list_with_all_params(self, client: Codex) -> None: + def test_method_list_with_all_params(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.list( project_id=0, answered_only=True, @@ -178,7 +178,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_raw_response_list(self, client: Codex) -> None: + def test_raw_response_list(self, client: CleanlabCodex) -> None: response = client.projects.knowledge.with_raw_response.list( project_id=0, ) @@ -189,7 +189,7 @@ def test_raw_response_list(self, client: Codex) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Codex) -> None: + def test_streaming_response_list(self, client: CleanlabCodex) -> None: with client.projects.knowledge.with_streaming_response.list( project_id=0, ) as response: @@ -202,7 +202,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: Codex) -> None: + def test_method_delete(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -210,7 +210,7 @@ def test_method_delete(self, client: Codex) -> None: assert knowledge is None @parametrize - def test_raw_response_delete(self, client: Codex) -> None: + def test_raw_response_delete(self, client: CleanlabCodex) -> None: response = client.projects.knowledge.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -222,7 +222,7 @@ def test_raw_response_delete(self, client: Codex) -> None: assert knowledge is None @parametrize - def test_streaming_response_delete(self, client: Codex) -> None: + def test_streaming_response_delete(self, client: CleanlabCodex) -> None: with client.projects.knowledge.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_delete(self, client: Codex) -> None: + def test_path_params_delete(self, client: CleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.delete( entry_id="", @@ -244,7 +244,7 @@ def test_path_params_delete(self, client: Codex) -> None: ) @parametrize - def test_method_add_question(self, client: Codex) -> None: + def test_method_add_question(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.add_question( project_id=0, question="question", @@ -252,7 +252,7 @@ def test_method_add_question(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_add_question(self, client: Codex) -> None: + def test_raw_response_add_question(self, client: CleanlabCodex) -> None: response = client.projects.knowledge.with_raw_response.add_question( project_id=0, question="question", @@ -264,7 +264,7 @@ def test_raw_response_add_question(self, client: Codex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_add_question(self, client: Codex) -> None: + def test_streaming_response_add_question(self, client: CleanlabCodex) -> None: with client.projects.knowledge.with_streaming_response.add_question( project_id=0, question="question", @@ -278,7 +278,7 @@ def test_streaming_response_add_question(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_query(self, client: Codex) -> None: + def test_method_query(self, client: CleanlabCodex) -> None: knowledge = client.projects.knowledge.query( project_id=0, question="question", @@ -286,7 +286,7 @@ def test_method_query(self, client: Codex) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - def test_raw_response_query(self, client: Codex) -> None: + def test_raw_response_query(self, client: CleanlabCodex) -> None: response = client.projects.knowledge.with_raw_response.query( project_id=0, question="question", @@ -298,7 +298,7 @@ def test_raw_response_query(self, client: Codex) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - def test_streaming_response_query(self, client: Codex) -> None: + def test_streaming_response_query(self, client: CleanlabCodex) -> None: with client.projects.knowledge.with_streaming_response.query( project_id=0, question="question", @@ -316,7 +316,7 @@ class TestAsyncKnowledge: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCodex) -> None: + async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.create( project_id=0, question="question", @@ -324,7 +324,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.create( project_id=0, question="question", @@ -333,7 +333,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.create( project_id=0, question="question", @@ -345,7 +345,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.create( project_id=0, question="question", @@ -359,7 +359,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -367,7 +367,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -379,7 +379,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -393,7 +393,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.retrieve( entry_id="", @@ -401,7 +401,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: ) @parametrize - async def test_method_update(self, async_client: AsyncCodex) -> None: + async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -409,7 +409,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -419,7 +419,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCodex) -> None: + async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -431,7 +431,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -445,7 +445,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_update(self, async_client: AsyncCodex) -> None: + async def test_path_params_update(self, async_client: AsyncCleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.update( entry_id="", @@ -453,14 +453,14 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: ) @parametrize - async def test_method_list(self, async_client: AsyncCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.list( project_id=0, ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + async def test_method_list_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.list( project_id=0, answered_only=True, @@ -473,7 +473,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.list( project_id=0, ) @@ -484,7 +484,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.list( project_id=0, ) as response: @@ -497,7 +497,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCodex) -> None: + async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -505,7 +505,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: assert knowledge is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -517,7 +517,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: assert knowledge is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -531,7 +531,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + async def test_path_params_delete(self, async_client: AsyncCleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.delete( entry_id="", @@ -539,7 +539,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: ) @parametrize - async def test_method_add_question(self, async_client: AsyncCodex) -> None: + async def test_method_add_question(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.add_question( project_id=0, question="question", @@ -547,7 +547,7 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: + async def test_raw_response_add_question(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.add_question( project_id=0, question="question", @@ -559,7 +559,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_add_question(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.add_question( project_id=0, question="question", @@ -573,7 +573,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCodex) - assert cast(Any, response.is_closed) is True @parametrize - async def test_method_query(self, async_client: AsyncCodex) -> None: + async def test_method_query(self, async_client: AsyncCleanlabCodex) -> None: knowledge = await async_client.projects.knowledge.query( project_id=0, question="question", @@ -581,7 +581,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - async def test_raw_response_query(self, async_client: AsyncCodex) -> None: + async def test_raw_response_query(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.query( project_id=0, question="question", @@ -593,7 +593,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_query(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.query( project_id=0, question="question", diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py index be4729ab..268c45e2 100644 --- a/tests/api_resources/test_health.py +++ b/tests/api_resources/test_health.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from codex.types import HealthCheckResponse from tests.utils import assert_matches_type @@ -18,12 +18,12 @@ class TestHealth: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_check(self, client: Codex) -> None: + def test_method_check(self, client: CleanlabCodex) -> None: health = client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_check(self, client: Codex) -> None: + def test_raw_response_check(self, client: CleanlabCodex) -> None: response = client.health.with_raw_response.check() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_check(self, client: Codex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_check(self, client: Codex) -> None: + def test_streaming_response_check(self, client: CleanlabCodex) -> None: with client.health.with_streaming_response.check() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -43,12 +43,12 @@ def test_streaming_response_check(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_db(self, client: Codex) -> None: + def test_method_db(self, client: CleanlabCodex) -> None: health = client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_db(self, client: Codex) -> None: + def test_raw_response_db(self, client: CleanlabCodex) -> None: response = client.health.with_raw_response.db() assert response.is_closed is True @@ -57,7 +57,7 @@ def test_raw_response_db(self, client: Codex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_db(self, client: Codex) -> None: + def test_streaming_response_db(self, client: CleanlabCodex) -> None: with client.health.with_streaming_response.db() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -68,12 +68,12 @@ def test_streaming_response_db(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_weaviate(self, client: Codex) -> None: + def test_method_weaviate(self, client: CleanlabCodex) -> None: health = client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_weaviate(self, client: Codex) -> None: + def test_raw_response_weaviate(self, client: CleanlabCodex) -> None: response = client.health.with_raw_response.weaviate() assert response.is_closed is True @@ -82,7 +82,7 @@ def test_raw_response_weaviate(self, client: Codex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_weaviate(self, client: Codex) -> None: + def test_streaming_response_weaviate(self, client: CleanlabCodex) -> None: with client.health.with_streaming_response.weaviate() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -97,12 +97,12 @@ class TestAsyncHealth: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_check(self, async_client: AsyncCodex) -> None: + async def test_method_check(self, async_client: AsyncCleanlabCodex) -> None: health = await async_client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_check(self, async_client: AsyncCodex) -> None: + async def test_raw_response_check(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.health.with_raw_response.check() assert response.is_closed is True @@ -111,7 +111,7 @@ async def test_raw_response_check(self, async_client: AsyncCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_check(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.health.with_streaming_response.check() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -122,12 +122,12 @@ async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_method_db(self, async_client: AsyncCodex) -> None: + async def test_method_db(self, async_client: AsyncCleanlabCodex) -> None: health = await async_client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_db(self, async_client: AsyncCodex) -> None: + async def test_raw_response_db(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.health.with_raw_response.db() assert response.is_closed is True @@ -136,7 +136,7 @@ async def test_raw_response_db(self, async_client: AsyncCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_db(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.health.with_streaming_response.db() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -147,12 +147,12 @@ async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_method_weaviate(self, async_client: AsyncCodex) -> None: + async def test_method_weaviate(self, async_client: AsyncCleanlabCodex) -> None: health = await async_client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None: + async def test_raw_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.health.with_raw_response.weaviate() assert response.is_closed is True @@ -161,7 +161,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.health.with_streaming_response.weaviate() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index 2d627c70..bfca62d9 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from codex.types import OrganizationSchemaPublic from tests.utils import assert_matches_type @@ -18,14 +18,14 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: Codex) -> None: + def test_method_retrieve(self, client: CleanlabCodex) -> None: organization = client.organizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Codex) -> None: + def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: response = client.organizations.with_raw_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -36,7 +36,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Codex) -> None: + def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: with client.organizations.with_streaming_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -49,7 +49,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_retrieve(self, client: Codex) -> None: + def test_path_params_retrieve(self, client: CleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.with_raw_response.retrieve( "", @@ -60,14 +60,14 @@ class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: organization = await async_client.organizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.organizations.with_raw_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -78,7 +78,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.organizations.with_streaming_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -91,7 +91,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.with_raw_response.retrieve( "", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 7e79d8e8..64dbec49 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from codex.types import ( ProjectListResponse, ProjectReturnSchema, @@ -21,7 +21,7 @@ class TestProjects: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: Codex) -> None: + def test_method_create(self, client: CleanlabCodex) -> None: project = client.projects.create( config={}, name="name", @@ -30,7 +30,7 @@ def test_method_create(self, client: Codex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: Codex) -> None: + def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: project = client.projects.create( config={"max_distance": 0}, name="name", @@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_create(self, client: Codex) -> None: + def test_raw_response_create(self, client: CleanlabCodex) -> None: response = client.projects.with_raw_response.create( config={}, name="name", @@ -53,7 +53,7 @@ def test_raw_response_create(self, client: Codex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_create(self, client: Codex) -> None: + def test_streaming_response_create(self, client: CleanlabCodex) -> None: with client.projects.with_streaming_response.create( config={}, name="name", @@ -68,14 +68,14 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: Codex) -> None: + def test_method_retrieve(self, client: CleanlabCodex) -> None: project = client.projects.retrieve( 0, ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Codex) -> None: + def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: response = client.projects.with_raw_response.retrieve( 0, ) @@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Codex) -> None: + def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: with client.projects.with_streaming_response.retrieve( 0, ) as response: @@ -99,7 +99,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_update(self, client: Codex) -> None: + def test_method_update(self, client: CleanlabCodex) -> None: project = client.projects.update( project_id=0, config={}, @@ -108,7 +108,7 @@ def test_method_update(self, client: Codex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: Codex) -> None: + def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: project = client.projects.update( project_id=0, config={"max_distance": 0}, @@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_update(self, client: Codex) -> None: + def test_raw_response_update(self, client: CleanlabCodex) -> None: response = client.projects.with_raw_response.update( project_id=0, config={}, @@ -131,7 +131,7 @@ def test_raw_response_update(self, client: Codex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_update(self, client: Codex) -> None: + def test_streaming_response_update(self, client: CleanlabCodex) -> None: with client.projects.with_streaming_response.update( project_id=0, config={}, @@ -146,14 +146,14 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_list(self, client: Codex) -> None: + def test_method_list(self, client: CleanlabCodex) -> None: project = client.projects.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - def test_raw_response_list(self, client: Codex) -> None: + def test_raw_response_list(self, client: CleanlabCodex) -> None: response = client.projects.with_raw_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -164,7 +164,7 @@ def test_raw_response_list(self, client: Codex) -> None: assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Codex) -> None: + def test_streaming_response_list(self, client: CleanlabCodex) -> None: with client.projects.with_streaming_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -177,14 +177,14 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: Codex) -> None: + def test_method_delete(self, client: CleanlabCodex) -> None: project = client.projects.delete( 0, ) assert project is None @parametrize - def test_raw_response_delete(self, client: Codex) -> None: + def test_raw_response_delete(self, client: CleanlabCodex) -> None: response = client.projects.with_raw_response.delete( 0, ) @@ -195,7 +195,7 @@ def test_raw_response_delete(self, client: Codex) -> None: assert project is None @parametrize - def test_streaming_response_delete(self, client: Codex) -> None: + def test_streaming_response_delete(self, client: CleanlabCodex) -> None: with client.projects.with_streaming_response.delete( 0, ) as response: @@ -208,14 +208,14 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_export(self, client: Codex) -> None: + def test_method_export(self, client: CleanlabCodex) -> None: project = client.projects.export( 0, ) assert_matches_type(object, project, path=["response"]) @parametrize - def test_raw_response_export(self, client: Codex) -> None: + def test_raw_response_export(self, client: CleanlabCodex) -> None: response = client.projects.with_raw_response.export( 0, ) @@ -226,7 +226,7 @@ def test_raw_response_export(self, client: Codex) -> None: assert_matches_type(object, project, path=["response"]) @parametrize - def test_streaming_response_export(self, client: Codex) -> None: + def test_streaming_response_export(self, client: CleanlabCodex) -> None: with client.projects.with_streaming_response.export( 0, ) as response: @@ -243,7 +243,7 @@ class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCodex) -> None: + async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.create( config={}, name="name", @@ -252,7 +252,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.create( config={"max_distance": 0}, name="name", @@ -262,7 +262,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.with_raw_response.create( config={}, name="name", @@ -275,7 +275,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.with_streaming_response.create( config={}, name="name", @@ -290,14 +290,14 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.retrieve( 0, ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.with_raw_response.retrieve( 0, ) @@ -308,7 +308,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.with_streaming_response.retrieve( 0, ) as response: @@ -321,7 +321,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True @parametrize - async def test_method_update(self, async_client: AsyncCodex) -> None: + async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.update( project_id=0, config={}, @@ -330,7 +330,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.update( project_id=0, config={"max_distance": 0}, @@ -340,7 +340,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCodex) -> None: + async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.with_raw_response.update( project_id=0, config={}, @@ -353,7 +353,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.with_streaming_response.update( project_id=0, config={}, @@ -368,14 +368,14 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_method_list(self, async_client: AsyncCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.with_raw_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -386,7 +386,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.with_streaming_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -399,14 +399,14 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCodex) -> None: + async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.delete( 0, ) assert project is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.with_raw_response.delete( 0, ) @@ -417,7 +417,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: assert project is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.with_streaming_response.delete( 0, ) as response: @@ -430,14 +430,14 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True @parametrize - async def test_method_export(self, async_client: AsyncCodex) -> None: + async def test_method_export(self, async_client: AsyncCleanlabCodex) -> None: project = await async_client.projects.export( 0, ) assert_matches_type(object, project, path=["response"]) @parametrize - async def test_raw_response_export(self, async_client: AsyncCodex) -> None: + async def test_raw_response_export(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.projects.with_raw_response.export( 0, ) @@ -448,7 +448,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None: assert_matches_type(object, project, path=["response"]) @parametrize - async def test_streaming_response_export(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_export(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.projects.with_streaming_response.export( 0, ) as response: diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index 55bba35f..af5fb8b7 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from tests.utils import assert_matches_type from codex.types.users import UserSchema @@ -18,12 +18,12 @@ class TestAPIKey: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_refresh(self, client: Codex) -> None: + def test_method_refresh(self, client: CleanlabCodex) -> None: api_key = client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - def test_raw_response_refresh(self, client: Codex) -> None: + def test_raw_response_refresh(self, client: CleanlabCodex) -> None: response = client.users.myself.api_key.with_raw_response.refresh() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_refresh(self, client: Codex) -> None: assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - def test_streaming_response_refresh(self, client: Codex) -> None: + def test_streaming_response_refresh(self, client: CleanlabCodex) -> None: with client.users.myself.api_key.with_streaming_response.refresh() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncAPIKey: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_refresh(self, async_client: AsyncCodex) -> None: + async def test_method_refresh(self, async_client: AsyncCleanlabCodex) -> None: api_key = await async_client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: + async def test_raw_response_refresh(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.users.myself.api_key.with_raw_response.refresh() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_refresh(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.users.myself.api_key.with_streaming_response.refresh() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py index da485882..15fd00f8 100644 --- a/tests/api_resources/users/myself/test_organizations.py +++ b/tests/api_resources/users/myself/test_organizations.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from tests.utils import assert_matches_type from codex.types.users.myself import UserOrganizationsSchema @@ -18,12 +18,12 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_list(self, client: Codex) -> None: + def test_method_list(self, client: CleanlabCodex) -> None: organization = client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - def test_raw_response_list(self, client: Codex) -> None: + def test_raw_response_list(self, client: CleanlabCodex) -> None: response = client.users.myself.organizations.with_raw_response.list() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_list(self, client: Codex) -> None: assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Codex) -> None: + def test_streaming_response_list(self, client: CleanlabCodex) -> None: with client.users.myself.organizations.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_list(self, async_client: AsyncCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: organization = await async_client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.users.myself.organizations.with_raw_response.list() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.users.myself.organizations.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index a206d1f9..de484616 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -7,7 +7,7 @@ import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from tests.utils import assert_matches_type from codex.types.users import UserSchemaPublic @@ -18,12 +18,12 @@ class TestMyself: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: Codex) -> None: + def test_method_retrieve(self, client: CleanlabCodex) -> None: myself = client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Codex) -> None: + def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: response = client.users.myself.with_raw_response.retrieve() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Codex) -> None: + def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: with client.users.myself.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncMyself: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: myself = await async_client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: response = await async_client.users.myself.with_raw_response.retrieve() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: async with async_client.users.myself.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/conftest.py b/tests/conftest.py index 8823ef7a..7501cec9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import pytest from pytest_asyncio import is_async_test -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest @@ -30,20 +30,20 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: @pytest.fixture(scope="session") -def client(request: FixtureRequest) -> Iterator[Codex]: +def client(request: FixtureRequest) -> Iterator[CleanlabCodex]: strict = getattr(request, "param", True) if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Codex(base_url=base_url, _strict_response_validation=strict) as client: + with CleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client: yield client @pytest.fixture(scope="session") -async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]: +async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlabCodex]: strict = getattr(request, "param", True) if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client: + async with AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 4e76d238..485d19e0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -20,7 +20,7 @@ from respx import MockRouter from pydantic import ValidationError -from codex import Codex, AsyncCodex, APIResponseValidationError +from codex import CleanlabCodex, AsyncCleanlabCodex, APIResponseValidationError from codex._types import Omit from codex._models import BaseModel, FinalRequestOptions from codex._constants import RAW_RESPONSE_HEADER @@ -42,7 +42,7 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 -def _get_open_connections(client: Codex | AsyncCodex) -> int: +def _get_open_connections(client: CleanlabCodex | AsyncCleanlabCodex) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -50,8 +50,8 @@ def _get_open_connections(client: Codex | AsyncCodex) -> int: return len(pool._requests) -class TestCodex: - client = Codex(base_url=base_url, _strict_response_validation=True) +class TestCleanlabCodex: + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -94,7 +94,7 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -126,7 +126,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -249,7 +249,7 @@ def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -258,7 +258,7 @@ def test_client_timeout_option(self) -> None: 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: - client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -266,7 +266,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: - client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -274,7 +274,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -283,15 +283,15 @@ def test_http_client_timeout_option(self) -> None: async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: - Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) + CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) def test_default_headers_option(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = Codex( + client2 = CleanlabCodex( base_url=base_url, _strict_response_validation=True, default_headers={ @@ -304,7 +304,9 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) + client = CleanlabCodex( + base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"} + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -416,7 +418,7 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, client: Codex) -> None: + def test_multipart_repeating_array(self, client: CleanlabCodex) -> None: request = client._build_request( FinalRequestOptions.construct( method="get", @@ -503,7 +505,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True) + client = CleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -511,23 +513,23 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" def test_base_url_env(self) -> None: - with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): - client = Codex(_strict_response_validation=True) + with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): + client = CleanlabCodex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness - with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - Codex(_strict_response_validation=True, environment="production") + CleanlabCodex(_strict_response_validation=True, environment="production") - client = Codex(base_url=None, _strict_response_validation=True, environment="production") + client = CleanlabCodex(base_url=None, _strict_response_validation=True, environment="production") assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", [ - Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - Codex( + CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + CleanlabCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -535,7 +537,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: Codex) -> None: + def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -548,8 +550,8 @@ def test_base_url_trailing_slash(self, client: Codex) -> None: @pytest.mark.parametrize( "client", [ - Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - Codex( + CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + CleanlabCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -557,7 +559,7 @@ def test_base_url_trailing_slash(self, client: Codex) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: Codex) -> None: + def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -570,8 +572,8 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None: @pytest.mark.parametrize( "client", [ - Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - Codex( + CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + CleanlabCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -579,7 +581,7 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: Codex) -> None: + def test_absolute_request_url(self, client: CleanlabCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -590,7 +592,7 @@ def test_absolute_request_url(self, client: Codex) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -601,7 +603,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -622,7 +624,7 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + CleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -631,12 +633,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Codex(base_url=base_url, _strict_response_validation=True) + strict_client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Codex(base_url=base_url, _strict_response_validation=False) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -664,7 +666,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True) + client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -707,7 +709,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non @pytest.mark.parametrize("failure_mode", ["status", "exception"]) def test_retries_taken( self, - client: Codex, + client: CleanlabCodex, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -737,7 +739,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_omit_retry_count_header(self, client: Codex, failures_before_success: int, respx_mock: MockRouter) -> None: + def test_omit_retry_count_header( + self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter + ) -> None: client = client.with_options(max_retries=4) nb_retries = 0 @@ -764,7 +768,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_overwrite_retry_count_header( - self, client: Codex, failures_before_success: int, respx_mock: MockRouter + self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) @@ -789,8 +793,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" -class TestAsyncCodex: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) +class TestAsyncCleanlabCodex: + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -835,7 +839,9 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = AsyncCleanlabCodex( + base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + ) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -867,7 +873,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -990,7 +996,7 @@ async def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) async def test_client_timeout_option(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -999,7 +1005,7 @@ async def test_client_timeout_option(self) -> None: 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: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1007,7 +1013,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1015,7 +1021,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1024,15 +1030,19 @@ async def test_http_client_timeout_option(self) -> None: def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: - AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) + AsyncCleanlabCodex( + base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client) + ) def test_default_headers_option(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = AsyncCleanlabCodex( + base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncCodex( + client2 = AsyncCleanlabCodex( base_url=base_url, _strict_response_validation=True, default_headers={ @@ -1045,7 +1055,9 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) + client = AsyncCleanlabCodex( + base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"} + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -1157,7 +1169,7 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None: + def test_multipart_repeating_array(self, async_client: AsyncCleanlabCodex) -> None: request = async_client._build_request( FinalRequestOptions.construct( method="get", @@ -1244,7 +1256,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True) + client = AsyncCleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -1252,23 +1264,23 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" def test_base_url_env(self) -> None: - with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): - client = AsyncCodex(_strict_response_validation=True) + with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): + client = AsyncCleanlabCodex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness - with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - AsyncCodex(_strict_response_validation=True, environment="production") + AsyncCleanlabCodex(_strict_response_validation=True, environment="production") - client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production") + client = AsyncCleanlabCodex(base_url=None, _strict_response_validation=True, environment="production") assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", [ - AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCodex( + AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCleanlabCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1276,7 +1288,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: + def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1289,8 +1301,8 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: @pytest.mark.parametrize( "client", [ - AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCodex( + AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCleanlabCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1298,7 +1310,7 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: + def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1311,8 +1323,8 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: @pytest.mark.parametrize( "client", [ - AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCodex( + AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCleanlabCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1320,7 +1332,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncCodex) -> None: + def test_absolute_request_url(self, client: AsyncCleanlabCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1331,7 +1343,7 @@ def test_absolute_request_url(self, client: AsyncCodex) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1343,7 +1355,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1365,7 +1377,7 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -1375,12 +1387,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + strict_client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncCodex(base_url=base_url, _strict_response_validation=False) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1409,7 +1421,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -1453,7 +1465,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, - async_client: AsyncCodex, + async_client: AsyncCleanlabCodex, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -1485,7 +1497,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio async def test_omit_retry_count_header( - self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) @@ -1514,7 +1526,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio async def test_overwrite_retry_count_header( - self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) diff --git a/tests/test_response.py b/tests/test_response.py index 224eb22e..b722c155 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -6,7 +6,7 @@ import pytest import pydantic -from codex import Codex, BaseModel, AsyncCodex +from codex import BaseModel, CleanlabCodex, AsyncCleanlabCodex from codex._response import ( APIResponse, BaseAPIResponse, @@ -56,7 +56,7 @@ def test_extract_response_type_binary_response() -> None: class PydanticModel(pydantic.BaseModel): ... -def test_response_parse_mismatched_basemodel(client: Codex) -> None: +def test_response_parse_mismatched_basemodel(client: CleanlabCodex) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -74,7 +74,7 @@ def test_response_parse_mismatched_basemodel(client: Codex) -> None: @pytest.mark.asyncio -async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCodex) -> None: +async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlabCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -91,7 +91,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCode await response.parse(to=PydanticModel) -def test_response_parse_custom_stream(client: Codex) -> None: +def test_response_parse_custom_stream(client: CleanlabCodex) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -106,7 +106,7 @@ def test_response_parse_custom_stream(client: Codex) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_stream(async_client: AsyncCodex) -> None: +async def test_async_response_parse_custom_stream(async_client: AsyncCleanlabCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -125,7 +125,7 @@ class CustomModel(BaseModel): bar: int -def test_response_parse_custom_model(client: Codex) -> None: +def test_response_parse_custom_model(client: CleanlabCodex) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -141,7 +141,7 @@ def test_response_parse_custom_model(client: Codex) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> None: +async def test_async_response_parse_custom_model(async_client: AsyncCleanlabCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -156,7 +156,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> No assert obj.bar == 2 -def test_response_parse_annotated_type(client: Codex) -> None: +def test_response_parse_annotated_type(client: CleanlabCodex) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -173,7 +173,7 @@ def test_response_parse_annotated_type(client: Codex) -> None: assert obj.bar == 2 -async def test_async_response_parse_annotated_type(async_client: AsyncCodex) -> None: +async def test_async_response_parse_annotated_type(async_client: AsyncCleanlabCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -201,7 +201,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncCodex) -> ("FalSe", False), ], ) -def test_response_parse_bool(client: Codex, content: str, expected: bool) -> None: +def test_response_parse_bool(client: CleanlabCodex, content: str, expected: bool) -> None: response = APIResponse( raw=httpx.Response(200, content=content), client=client, @@ -226,7 +226,7 @@ def test_response_parse_bool(client: Codex, content: str, expected: bool) -> Non ("FalSe", False), ], ) -async def test_async_response_parse_bool(client: AsyncCodex, content: str, expected: bool) -> None: +async def test_async_response_parse_bool(client: AsyncCleanlabCodex, content: str, expected: bool) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=content), client=client, @@ -245,7 +245,7 @@ class OtherModel(BaseModel): @pytest.mark.parametrize("client", [False], indirect=True) # loose validation -def test_response_parse_expect_model_union_non_json_content(client: Codex) -> None: +def test_response_parse_expect_model_union_non_json_content(client: CleanlabCodex) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=client, @@ -262,7 +262,7 @@ def test_response_parse_expect_model_union_non_json_content(client: Codex) -> No @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: AsyncCodex) -> None: +async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCleanlabCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=async_client, diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 443b5aa5..b6b669a6 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -5,13 +5,13 @@ import httpx import pytest -from codex import Codex, AsyncCodex +from codex import CleanlabCodex, AsyncCleanlabCodex from codex._streaming import Stream, AsyncStream, ServerSentEvent @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_basic(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_basic(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: def body() -> Iterator[bytes]: yield b"event: completion\n" yield b'data: {"foo":true}\n' @@ -28,7 +28,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_missing_event(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_data_missing_event(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: def body() -> Iterator[bytes]: yield b'data: {"foo":true}\n' yield b"\n" @@ -44,7 +44,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_event_missing_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_event_missing_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -60,7 +60,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_multiple_events(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -82,7 +82,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events_with_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_multiple_events_with_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo":true}\n' @@ -106,7 +106,9 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines_with_empty_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_multiple_data_lines_with_empty_line( + sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex +) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -128,7 +130,9 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_json_escaped_double_new_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_data_json_escaped_double_new_line( + sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex +) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo": "my long\\n\\ncontent"}' @@ -145,7 +149,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines(sync: bool, client: Codex, async_client: AsyncCodex) -> None: +async def test_multiple_data_lines(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -165,8 +169,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_special_new_line_character( sync: bool, - client: Codex, - async_client: AsyncCodex, + client: CleanlabCodex, + async_client: AsyncCleanlabCodex, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":" culpa"}\n' @@ -196,8 +200,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_multi_byte_character_multiple_chunks( sync: bool, - client: Codex, - async_client: AsyncCodex, + client: CleanlabCodex, + async_client: AsyncCleanlabCodex, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":"' @@ -237,8 +241,8 @@ def make_event_iterator( content: Iterator[bytes], *, sync: bool, - client: Codex, - async_client: AsyncCodex, + client: CleanlabCodex, + async_client: AsyncCleanlabCodex, ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: if sync: return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() From b012d46ac7d3053f7996d471e69e3a515df75fde Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:04:51 +0000 Subject: [PATCH 005/320] fix: update organization name --- LICENSE | 2 +- README.md | 46 ++--- SECURITY.md | 4 +- pyproject.toml | 4 +- src/codex/__init__.py | 12 +- src/codex/_client.py | 56 +++--- src/codex/_exceptions.py | 4 +- src/codex/_resource.py | 10 +- src/codex/_response.py | 4 +- src/codex/_streaming.py | 6 +- src/codex/_utils/_logs.py | 2 +- .../organizations/test_billing.py | 34 ++-- .../projects/test_access_keys.py | 82 ++++----- .../api_resources/projects/test_knowledge.py | 110 ++++++------ tests/api_resources/test_health.py | 38 ++--- tests/api_resources/test_organizations.py | 18 +- tests/api_resources/test_projects.py | 82 ++++----- .../users/myself/test_api_key.py | 14 +- .../users/myself/test_organizations.py | 14 +- tests/api_resources/users/test_myself.py | 14 +- tests/conftest.py | 10 +- tests/test_client.py | 160 +++++++++--------- tests/test_response.py | 26 +-- tests/test_streaming.py | 34 ++-- 24 files changed, 387 insertions(+), 399 deletions(-) diff --git a/LICENSE b/LICENSE index 468d5b60..e575118f 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Cleanlab Codex + Copyright 2025 Cleanlab 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 1e28f2a7..b156eb36 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Cleanlab Codex Python API library +# Cleanlab Python API library [![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/) -The Cleanlab Codex Python library provides convenient access to the Cleanlab Codex REST API from any Python 3.8+ +The Cleanlab Python library provides convenient access to the Cleanlab REST API from any Python 3.8+ 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). @@ -27,9 +27,9 @@ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git The full API of this library can be found in [api.md](api.md). ```python -from codex import CleanlabCodex +from codex import Cleanlab -client = CleanlabCodex( +client = Cleanlab( # or 'production' | 'local'; defaults to "production". environment="staging", ) @@ -49,13 +49,13 @@ so that your Bearer Token is not stored in source control. ## Async usage -Simply import `AsyncCleanlabCodex` instead of `CleanlabCodex` and use `await` with each API call: +Simply import `AsyncCleanlab` instead of `Cleanlab` and use `await` with each API call: ```python import asyncio -from codex import AsyncCleanlabCodex +from codex import AsyncCleanlab -client = AsyncCleanlabCodex( +client = AsyncCleanlab( # or 'production' | 'local'; defaults to "production". environment="staging", ) @@ -95,9 +95,9 @@ All errors inherit from `codex.APIError`. ```python import codex -from codex import CleanlabCodex +from codex import Cleanlab -client = CleanlabCodex() +client = Cleanlab() try: client.projects.create( @@ -138,10 +138,10 @@ Connection errors (for example, due to a network connectivity problem), 408 Requ You can use the `max_retries` option to configure or disable retry settings: ```python -from codex import CleanlabCodex +from codex import Cleanlab # Configure the default for all requests: -client = CleanlabCodex( +client = Cleanlab( # default is 2 max_retries=0, ) @@ -160,16 +160,16 @@ By default requests time out after 1 minute. You can configure this with a `time which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: ```python -from codex import CleanlabCodex +from codex import Cleanlab # Configure the default for all requests: -client = CleanlabCodex( +client = Cleanlab( # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: -client = CleanlabCodex( +client = Cleanlab( timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) @@ -191,10 +191,10 @@ 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 `CLEANLAB_CODEX_LOG` to `info`. +You can enable logging by setting the environment variable `CLEANLAB_LOG` to `info`. ```shell -$ export CLEANLAB_CODEX_LOG=info +$ export CLEANLAB_LOG=info ``` Or to `debug` for more verbose logging. @@ -216,9 +216,9 @@ if response.my_field is None: The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., ```py -from codex import CleanlabCodex +from codex import Cleanlab -client = CleanlabCodex() +client = Cleanlab() response = client.projects.with_raw_response.create( config={}, name="name", @@ -298,10 +298,10 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c ```python import httpx -from codex import CleanlabCodex, DefaultHttpxClient +from codex import Cleanlab, DefaultHttpxClient -client = CleanlabCodex( - # Or use the `CLEANLAB_CODEX_BASE_URL` env var +client = Cleanlab( + # Or use the `CLEANLAB_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( proxy="http://my.test.proxy.example.com", @@ -321,9 +321,9 @@ 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 codex import CleanlabCodex +from codex import Cleanlab -with CleanlabCodex() as client: +with Cleanlab() as client: # make requests here ... diff --git a/SECURITY.md b/SECURITY.md index c229abd4..2ca30344 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,9 +16,9 @@ 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 Cleanlab Codex please follow the respective company's security reporting guidelines. +or products provided by Cleanlab please follow the respective company's security reporting guidelines. -### Cleanlab Codex Terms and Policies +### Cleanlab Terms and Policies Please contact support@cleanlab.ai for any questions or concerns regarding security of our services. diff --git a/pyproject.toml b/pyproject.toml index 86518e14..5ba1074c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "codex" version = "0.0.1-alpha.0" -description = "The official Python library for the Cleanlab Codex API" +description = "The official Python library for the Cleanlab API" dynamic = ["readme"] license = "Apache-2.0" authors = [ -{ name = "Cleanlab Codex", email = "support@cleanlab.ai" }, +{ name = "Cleanlab", email = "support@cleanlab.ai" }, ] dependencies = [ "httpx>=0.23.0, <1", diff --git a/src/codex/__init__.py b/src/codex/__init__.py index 62559f31..e3c0220d 100644 --- a/src/codex/__init__.py +++ b/src/codex/__init__.py @@ -8,12 +8,12 @@ Client, Stream, Timeout, + Cleanlab, Transport, AsyncClient, AsyncStream, - CleanlabCodex, + AsyncCleanlab, RequestOptions, - AsyncCleanlabCodex, ) from ._models import BaseModel from ._version import __title__, __version__ @@ -21,6 +21,7 @@ from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS from ._exceptions import ( APIError, + CleanlabError, ConflictError, NotFoundError, APIStatusError, @@ -28,7 +29,6 @@ APITimeoutError, BadRequestError, APIConnectionError, - CleanlabCodexError, AuthenticationError, InternalServerError, PermissionDeniedError, @@ -48,7 +48,7 @@ "NotGiven", "NOT_GIVEN", "Omit", - "CleanlabCodexError", + "CleanlabError", "APIError", "APIStatusError", "APITimeoutError", @@ -68,8 +68,8 @@ "AsyncClient", "Stream", "AsyncStream", - "CleanlabCodex", - "AsyncCleanlabCodex", + "Cleanlab", + "AsyncCleanlab", "ENVIRONMENTS", "file_from_path", "BaseModel", diff --git a/src/codex/_client.py b/src/codex/_client.py index 3a0f8657..0d3b6b8d 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -43,8 +43,8 @@ "Transport", "ProxiesTypes", "RequestOptions", - "CleanlabCodex", - "AsyncCleanlabCodex", + "Cleanlab", + "AsyncCleanlab", "Client", "AsyncClient", ] @@ -56,13 +56,13 @@ } -class CleanlabCodex(SyncAPIClient): +class Cleanlab(SyncAPIClient): health: health.HealthResource organizations: organizations.OrganizationsResource users: users.UsersResource projects: projects.ProjectsResource - with_raw_response: CleanlabCodexWithRawResponse - with_streaming_response: CleanlabCodexWithStreamedResponse + with_raw_response: CleanlabWithRawResponse + with_streaming_response: CleanlabWithStreamedResponse # client options bearer_token: str | None @@ -97,7 +97,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous Cleanlab Codex client instance. + """Construct a new synchronous Cleanlab client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `bearer_token` from `BEARER_TOKEN` @@ -118,14 +118,14 @@ def __init__( self._environment = environment - base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL") + base_url_env = os.environ.get("CLEANLAB_BASE_URL") if is_given(base_url) and base_url is not None: # cast required because mypy doesn't understand the type narrowing base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] elif is_given(environment): if base_url_env and base_url is not None: raise ValueError( - "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", ) try: @@ -157,8 +157,8 @@ def __init__( self.organizations = organizations.OrganizationsResource(self) self.users = users.UsersResource(self) self.projects = projects.ProjectsResource(self) - self.with_raw_response = CleanlabCodexWithRawResponse(self) - self.with_streaming_response = CleanlabCodexWithStreamedResponse(self) + self.with_raw_response = CleanlabWithRawResponse(self) + self.with_streaming_response = CleanlabWithStreamedResponse(self) @property @override @@ -318,13 +318,13 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class AsyncCleanlabCodex(AsyncAPIClient): +class AsyncCleanlab(AsyncAPIClient): health: health.AsyncHealthResource organizations: organizations.AsyncOrganizationsResource users: users.AsyncUsersResource projects: projects.AsyncProjectsResource - with_raw_response: AsyncCleanlabCodexWithRawResponse - with_streaming_response: AsyncCleanlabCodexWithStreamedResponse + with_raw_response: AsyncCleanlabWithRawResponse + with_streaming_response: AsyncCleanlabWithStreamedResponse # client options bearer_token: str | None @@ -359,7 +359,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async Cleanlab Codex client instance. + """Construct a new async Cleanlab client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `bearer_token` from `BEARER_TOKEN` @@ -380,14 +380,14 @@ def __init__( self._environment = environment - base_url_env = os.environ.get("CLEANLAB_CODEX_BASE_URL") + base_url_env = os.environ.get("CLEANLAB_BASE_URL") if is_given(base_url) and base_url is not None: # cast required because mypy doesn't understand the type narrowing base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] elif is_given(environment): if base_url_env and base_url is not None: raise ValueError( - "Ambiguous URL; The `CLEANLAB_CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", ) try: @@ -419,8 +419,8 @@ def __init__( self.organizations = organizations.AsyncOrganizationsResource(self) self.users = users.AsyncUsersResource(self) self.projects = projects.AsyncProjectsResource(self) - self.with_raw_response = AsyncCleanlabCodexWithRawResponse(self) - self.with_streaming_response = AsyncCleanlabCodexWithStreamedResponse(self) + self.with_raw_response = AsyncCleanlabWithRawResponse(self) + self.with_streaming_response = AsyncCleanlabWithStreamedResponse(self) @property @override @@ -580,38 +580,38 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class CleanlabCodexWithRawResponse: - def __init__(self, client: CleanlabCodex) -> None: +class CleanlabWithRawResponse: + def __init__(self, client: Cleanlab) -> None: self.health = health.HealthResourceWithRawResponse(client.health) self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations) self.users = users.UsersResourceWithRawResponse(client.users) self.projects = projects.ProjectsResourceWithRawResponse(client.projects) -class AsyncCleanlabCodexWithRawResponse: - def __init__(self, client: AsyncCleanlabCodex) -> None: +class AsyncCleanlabWithRawResponse: + def __init__(self, client: AsyncCleanlab) -> None: self.health = health.AsyncHealthResourceWithRawResponse(client.health) self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations) self.users = users.AsyncUsersResourceWithRawResponse(client.users) self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) -class CleanlabCodexWithStreamedResponse: - def __init__(self, client: CleanlabCodex) -> None: +class CleanlabWithStreamedResponse: + def __init__(self, client: Cleanlab) -> None: self.health = health.HealthResourceWithStreamingResponse(client.health) self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.UsersResourceWithStreamingResponse(client.users) self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) -class AsyncCleanlabCodexWithStreamedResponse: - def __init__(self, client: AsyncCleanlabCodex) -> None: +class AsyncCleanlabWithStreamedResponse: + def __init__(self, client: AsyncCleanlab) -> None: self.health = health.AsyncHealthResourceWithStreamingResponse(client.health) self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.AsyncUsersResourceWithStreamingResponse(client.users) self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) -Client = CleanlabCodex +Client = Cleanlab -AsyncClient = AsyncCleanlabCodex +AsyncClient = AsyncCleanlab diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py index c416e8d6..db0f4b62 100644 --- a/src/codex/_exceptions.py +++ b/src/codex/_exceptions.py @@ -18,11 +18,11 @@ ] -class CleanlabCodexError(Exception): +class CleanlabError(Exception): pass -class APIError(CleanlabCodexError): +class APIError(CleanlabError): message: str request: httpx.Request diff --git a/src/codex/_resource.py b/src/codex/_resource.py index bfbd7eef..1bf30f6b 100644 --- a/src/codex/_resource.py +++ b/src/codex/_resource.py @@ -8,13 +8,13 @@ import anyio if TYPE_CHECKING: - from ._client import CleanlabCodex, AsyncCleanlabCodex + from ._client import Cleanlab, AsyncCleanlab class SyncAPIResource: - _client: CleanlabCodex + _client: Cleanlab - def __init__(self, client: CleanlabCodex) -> None: + def __init__(self, client: Cleanlab) -> None: self._client = client self._get = client.get self._post = client.post @@ -28,9 +28,9 @@ def _sleep(self, seconds: float) -> None: class AsyncAPIResource: - _client: AsyncCleanlabCodex + _client: AsyncCleanlab - def __init__(self, client: AsyncCleanlabCodex) -> None: + def __init__(self, client: AsyncCleanlab) -> None: self._client = client self._get = client.get self._post = client.post diff --git a/src/codex/_response.py b/src/codex/_response.py index 3cbf40e5..4faa64ef 100644 --- a/src/codex/_response.py +++ b/src/codex/_response.py @@ -29,7 +29,7 @@ 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 -from ._exceptions import CleanlabCodexError, APIResponseValidationError +from ._exceptions import CleanlabError, APIResponseValidationError if TYPE_CHECKING: from ._models import FinalRequestOptions @@ -554,7 +554,7 @@ def __init__(self) -> None: ) -class StreamAlreadyConsumed(CleanlabCodexError): +class StreamAlreadyConsumed(CleanlabError): """ Attempted to read or stream content, but the content has already been streamed. diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py index 820839f9..e545fc0f 100644 --- a/src/codex/_streaming.py +++ b/src/codex/_streaming.py @@ -12,7 +12,7 @@ from ._utils import extract_type_var_from_base if TYPE_CHECKING: - from ._client import CleanlabCodex, AsyncCleanlabCodex + from ._client import Cleanlab, AsyncCleanlab _T = TypeVar("_T") @@ -30,7 +30,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: CleanlabCodex, + client: Cleanlab, ) -> None: self.response = response self._cast_to = cast_to @@ -93,7 +93,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: AsyncCleanlabCodex, + client: AsyncCleanlab, ) -> None: self.response = response self._cast_to = cast_to diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py index 23658f29..58944f5f 100644 --- a/src/codex/_utils/_logs.py +++ b/src/codex/_utils/_logs.py @@ -14,7 +14,7 @@ def _basic_config() -> None: def setup_logging() -> None: - env = os.environ.get("CLEANLAB_CODEX_LOG") + env = os.environ.get("CLEANLAB_LOG") if env == "debug": _basic_config() logger.setLevel(logging.DEBUG) diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py index c2dbdedb..c1530b3b 100644 --- a/tests/api_resources/organizations/test_billing.py +++ b/tests/api_resources/organizations/test_billing.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from tests.utils import assert_matches_type from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema @@ -18,14 +18,14 @@ class TestBilling: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_invoices(self, client: CleanlabCodex) -> None: + def test_method_invoices(self, client: Cleanlab) -> None: billing = client.organizations.billing.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - def test_raw_response_invoices(self, client: CleanlabCodex) -> None: + def test_raw_response_invoices(self, client: Cleanlab) -> None: response = client.organizations.billing.with_raw_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -36,7 +36,7 @@ def test_raw_response_invoices(self, client: CleanlabCodex) -> None: assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - def test_streaming_response_invoices(self, client: CleanlabCodex) -> None: + def test_streaming_response_invoices(self, client: Cleanlab) -> None: with client.organizations.billing.with_streaming_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -49,21 +49,21 @@ def test_streaming_response_invoices(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_invoices(self, client: CleanlabCodex) -> None: + def test_path_params_invoices(self, client: Cleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.billing.with_raw_response.invoices( "", ) @parametrize - def test_method_usage(self, client: CleanlabCodex) -> None: + def test_method_usage(self, client: Cleanlab) -> None: billing = client.organizations.billing.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - def test_raw_response_usage(self, client: CleanlabCodex) -> None: + def test_raw_response_usage(self, client: Cleanlab) -> None: response = client.organizations.billing.with_raw_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -74,7 +74,7 @@ def test_raw_response_usage(self, client: CleanlabCodex) -> None: assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - def test_streaming_response_usage(self, client: CleanlabCodex) -> None: + def test_streaming_response_usage(self, client: Cleanlab) -> None: with client.organizations.billing.with_streaming_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -87,7 +87,7 @@ def test_streaming_response_usage(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_usage(self, client: CleanlabCodex) -> None: + def test_path_params_usage(self, client: Cleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.billing.with_raw_response.usage( "", @@ -98,14 +98,14 @@ class TestAsyncBilling: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_invoices(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_invoices(self, async_client: AsyncCleanlab) -> None: billing = await async_client.organizations.billing.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - async def test_raw_response_invoices(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_invoices(self, async_client: AsyncCleanlab) -> None: response = await async_client.organizations.billing.with_raw_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -116,7 +116,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCleanlabCodex) -> assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - async def test_streaming_response_invoices(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_invoices(self, async_client: AsyncCleanlab) -> None: async with async_client.organizations.billing.with_streaming_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -129,21 +129,21 @@ async def test_streaming_response_invoices(self, async_client: AsyncCleanlabCode assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_invoices(self, async_client: AsyncCleanlabCodex) -> None: + async def test_path_params_invoices(self, async_client: AsyncCleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.billing.with_raw_response.invoices( "", ) @parametrize - async def test_method_usage(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_usage(self, async_client: AsyncCleanlab) -> None: billing = await async_client.organizations.billing.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - async def test_raw_response_usage(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_usage(self, async_client: AsyncCleanlab) -> None: response = await async_client.organizations.billing.with_raw_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -154,7 +154,7 @@ async def test_raw_response_usage(self, async_client: AsyncCleanlabCodex) -> Non assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - async def test_streaming_response_usage(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_usage(self, async_client: AsyncCleanlab) -> None: async with async_client.organizations.billing.with_streaming_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -167,7 +167,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_usage(self, async_client: AsyncCleanlabCodex) -> None: + async def test_path_params_usage(self, async_client: AsyncCleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.billing.with_raw_response.usage( "", diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index 6828a32a..26f984b0 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from tests.utils import assert_matches_type from codex._utils import parse_datetime from codex.types.projects import ( @@ -22,7 +22,7 @@ class TestAccessKeys: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: CleanlabCodex) -> None: + def test_method_create(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.create( project_id=0, name="name", @@ -30,7 +30,7 @@ def test_method_create(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: + def test_method_create_with_all_params(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.create( project_id=0, name="name", @@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_create(self, client: CleanlabCodex) -> None: + def test_raw_response_create(self, client: Cleanlab) -> None: response = client.projects.access_keys.with_raw_response.create( project_id=0, name="name", @@ -52,7 +52,7 @@ def test_raw_response_create(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_create(self, client: CleanlabCodex) -> None: + def test_streaming_response_create(self, client: Cleanlab) -> None: with client.projects.access_keys.with_streaming_response.create( project_id=0, name="name", @@ -66,7 +66,7 @@ def test_streaming_response_create(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: CleanlabCodex) -> None: + def test_method_retrieve(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.retrieve( access_key_id=0, project_id=0, @@ -74,7 +74,7 @@ def test_method_retrieve(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: + def test_raw_response_retrieve(self, client: Cleanlab) -> None: response = client.projects.access_keys.with_raw_response.retrieve( access_key_id=0, project_id=0, @@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: + def test_streaming_response_retrieve(self, client: Cleanlab) -> None: with client.projects.access_keys.with_streaming_response.retrieve( access_key_id=0, project_id=0, @@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_update(self, client: CleanlabCodex) -> None: + def test_method_update(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -109,7 +109,7 @@ def test_method_update(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: + def test_method_update_with_all_params(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -120,7 +120,7 @@ def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_update(self, client: CleanlabCodex) -> None: + def test_raw_response_update(self, client: Cleanlab) -> None: response = client.projects.access_keys.with_raw_response.update( access_key_id=0, project_id=0, @@ -133,7 +133,7 @@ def test_raw_response_update(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_update(self, client: CleanlabCodex) -> None: + def test_streaming_response_update(self, client: Cleanlab) -> None: with client.projects.access_keys.with_streaming_response.update( access_key_id=0, project_id=0, @@ -148,14 +148,14 @@ def test_streaming_response_update(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_list(self, client: CleanlabCodex) -> None: + def test_method_list(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.list( 0, ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - def test_raw_response_list(self, client: CleanlabCodex) -> None: + def test_raw_response_list(self, client: Cleanlab) -> None: response = client.projects.access_keys.with_raw_response.list( 0, ) @@ -166,7 +166,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None: assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - def test_streaming_response_list(self, client: CleanlabCodex) -> None: + def test_streaming_response_list(self, client: Cleanlab) -> None: with client.projects.access_keys.with_streaming_response.list( 0, ) as response: @@ -179,7 +179,7 @@ def test_streaming_response_list(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: CleanlabCodex) -> None: + def test_method_delete(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.delete( access_key_id=0, project_id=0, @@ -187,7 +187,7 @@ def test_method_delete(self, client: CleanlabCodex) -> None: assert access_key is None @parametrize - def test_raw_response_delete(self, client: CleanlabCodex) -> None: + def test_raw_response_delete(self, client: Cleanlab) -> None: response = client.projects.access_keys.with_raw_response.delete( access_key_id=0, project_id=0, @@ -199,7 +199,7 @@ def test_raw_response_delete(self, client: CleanlabCodex) -> None: assert access_key is None @parametrize - def test_streaming_response_delete(self, client: CleanlabCodex) -> None: + def test_streaming_response_delete(self, client: Cleanlab) -> None: with client.projects.access_keys.with_streaming_response.delete( access_key_id=0, project_id=0, @@ -213,7 +213,7 @@ def test_streaming_response_delete(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_revoke(self, client: CleanlabCodex) -> None: + def test_method_revoke(self, client: Cleanlab) -> None: access_key = client.projects.access_keys.revoke( access_key_id=0, project_id=0, @@ -221,7 +221,7 @@ def test_method_revoke(self, client: CleanlabCodex) -> None: assert access_key is None @parametrize - def test_raw_response_revoke(self, client: CleanlabCodex) -> None: + def test_raw_response_revoke(self, client: Cleanlab) -> None: response = client.projects.access_keys.with_raw_response.revoke( access_key_id=0, project_id=0, @@ -233,7 +233,7 @@ def test_raw_response_revoke(self, client: CleanlabCodex) -> None: assert access_key is None @parametrize - def test_streaming_response_revoke(self, client: CleanlabCodex) -> None: + def test_streaming_response_revoke(self, client: Cleanlab) -> None: with client.projects.access_keys.with_streaming_response.revoke( access_key_id=0, project_id=0, @@ -251,7 +251,7 @@ class TestAsyncAccessKeys: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_create(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.create( project_id=0, name="name", @@ -259,7 +259,7 @@ async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.create( project_id=0, name="name", @@ -269,7 +269,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCo assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.access_keys.with_raw_response.create( project_id=0, name="name", @@ -281,7 +281,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.access_keys.with_streaming_response.create( project_id=0, name="name", @@ -295,7 +295,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.retrieve( access_key_id=0, project_id=0, @@ -303,7 +303,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve( access_key_id=0, project_id=0, @@ -315,7 +315,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve( access_key_id=0, project_id=0, @@ -329,7 +329,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode assert cast(Any, response.is_closed) is True @parametrize - async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_update(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -338,7 +338,7 @@ async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -349,7 +349,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCo assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.access_keys.with_raw_response.update( access_key_id=0, project_id=0, @@ -362,7 +362,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.access_keys.with_streaming_response.update( access_key_id=0, project_id=0, @@ -377,14 +377,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.list( 0, ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.access_keys.with_raw_response.list( 0, ) @@ -395,7 +395,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.access_keys.with_streaming_response.list( 0, ) as response: @@ -408,7 +408,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) - assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_delete(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.delete( access_key_id=0, project_id=0, @@ -416,7 +416,7 @@ async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: assert access_key is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.access_keys.with_raw_response.delete( access_key_id=0, project_id=0, @@ -428,7 +428,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> No assert access_key is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.access_keys.with_streaming_response.delete( access_key_id=0, project_id=0, @@ -442,7 +442,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_revoke(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_revoke(self, async_client: AsyncCleanlab) -> None: access_key = await async_client.projects.access_keys.revoke( access_key_id=0, project_id=0, @@ -450,7 +450,7 @@ async def test_method_revoke(self, async_client: AsyncCleanlabCodex) -> None: assert access_key is None @parametrize - async def test_raw_response_revoke(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_revoke(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.access_keys.with_raw_response.revoke( access_key_id=0, project_id=0, @@ -462,7 +462,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCleanlabCodex) -> No assert access_key is None @parametrize - async def test_streaming_response_revoke(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_revoke(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.access_keys.with_streaming_response.revoke( access_key_id=0, project_id=0, diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py index e74a0bcc..9755583f 100644 --- a/tests/api_resources/projects/test_knowledge.py +++ b/tests/api_resources/projects/test_knowledge.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from tests.utils import assert_matches_type from codex.types.projects import ( Entry, @@ -21,7 +21,7 @@ class TestKnowledge: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: CleanlabCodex) -> None: + def test_method_create(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.create( project_id=0, question="question", @@ -29,7 +29,7 @@ def test_method_create(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: + def test_method_create_with_all_params(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.create( project_id=0, question="question", @@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_create(self, client: CleanlabCodex) -> None: + def test_raw_response_create(self, client: Cleanlab) -> None: response = client.projects.knowledge.with_raw_response.create( project_id=0, question="question", @@ -50,7 +50,7 @@ def test_raw_response_create(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_create(self, client: CleanlabCodex) -> None: + def test_streaming_response_create(self, client: Cleanlab) -> None: with client.projects.knowledge.with_streaming_response.create( project_id=0, question="question", @@ -64,7 +64,7 @@ def test_streaming_response_create(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: CleanlabCodex) -> None: + def test_method_retrieve(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -72,7 +72,7 @@ def test_method_retrieve(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: + def test_raw_response_retrieve(self, client: Cleanlab) -> None: response = client.projects.knowledge.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -84,7 +84,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: + def test_streaming_response_retrieve(self, client: Cleanlab) -> None: with client.projects.knowledge.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -98,7 +98,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_retrieve(self, client: CleanlabCodex) -> None: + def test_path_params_retrieve(self, client: Cleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.retrieve( entry_id="", @@ -106,7 +106,7 @@ def test_path_params_retrieve(self, client: CleanlabCodex) -> None: ) @parametrize - def test_method_update(self, client: CleanlabCodex) -> None: + def test_method_update(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -114,7 +114,7 @@ def test_method_update(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: + def test_method_update_with_all_params(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -124,7 +124,7 @@ def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_update(self, client: CleanlabCodex) -> None: + def test_raw_response_update(self, client: Cleanlab) -> None: response = client.projects.knowledge.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -136,7 +136,7 @@ def test_raw_response_update(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_update(self, client: CleanlabCodex) -> None: + def test_streaming_response_update(self, client: Cleanlab) -> None: with client.projects.knowledge.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -150,7 +150,7 @@ def test_streaming_response_update(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_update(self, client: CleanlabCodex) -> None: + def test_path_params_update(self, client: Cleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.update( entry_id="", @@ -158,14 +158,14 @@ def test_path_params_update(self, client: CleanlabCodex) -> None: ) @parametrize - def test_method_list(self, client: CleanlabCodex) -> None: + def test_method_list(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.list( project_id=0, ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_method_list_with_all_params(self, client: CleanlabCodex) -> None: + def test_method_list_with_all_params(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.list( project_id=0, answered_only=True, @@ -178,7 +178,7 @@ def test_method_list_with_all_params(self, client: CleanlabCodex) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_raw_response_list(self, client: CleanlabCodex) -> None: + def test_raw_response_list(self, client: Cleanlab) -> None: response = client.projects.knowledge.with_raw_response.list( project_id=0, ) @@ -189,7 +189,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_streaming_response_list(self, client: CleanlabCodex) -> None: + def test_streaming_response_list(self, client: Cleanlab) -> None: with client.projects.knowledge.with_streaming_response.list( project_id=0, ) as response: @@ -202,7 +202,7 @@ def test_streaming_response_list(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: CleanlabCodex) -> None: + def test_method_delete(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -210,7 +210,7 @@ def test_method_delete(self, client: CleanlabCodex) -> None: assert knowledge is None @parametrize - def test_raw_response_delete(self, client: CleanlabCodex) -> None: + def test_raw_response_delete(self, client: Cleanlab) -> None: response = client.projects.knowledge.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -222,7 +222,7 @@ def test_raw_response_delete(self, client: CleanlabCodex) -> None: assert knowledge is None @parametrize - def test_streaming_response_delete(self, client: CleanlabCodex) -> None: + def test_streaming_response_delete(self, client: Cleanlab) -> None: with client.projects.knowledge.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_delete(self, client: CleanlabCodex) -> None: + def test_path_params_delete(self, client: Cleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.delete( entry_id="", @@ -244,7 +244,7 @@ def test_path_params_delete(self, client: CleanlabCodex) -> None: ) @parametrize - def test_method_add_question(self, client: CleanlabCodex) -> None: + def test_method_add_question(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.add_question( project_id=0, question="question", @@ -252,7 +252,7 @@ def test_method_add_question(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_add_question(self, client: CleanlabCodex) -> None: + def test_raw_response_add_question(self, client: Cleanlab) -> None: response = client.projects.knowledge.with_raw_response.add_question( project_id=0, question="question", @@ -264,7 +264,7 @@ def test_raw_response_add_question(self, client: CleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_add_question(self, client: CleanlabCodex) -> None: + def test_streaming_response_add_question(self, client: Cleanlab) -> None: with client.projects.knowledge.with_streaming_response.add_question( project_id=0, question="question", @@ -278,7 +278,7 @@ def test_streaming_response_add_question(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_query(self, client: CleanlabCodex) -> None: + def test_method_query(self, client: Cleanlab) -> None: knowledge = client.projects.knowledge.query( project_id=0, question="question", @@ -286,7 +286,7 @@ def test_method_query(self, client: CleanlabCodex) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - def test_raw_response_query(self, client: CleanlabCodex) -> None: + def test_raw_response_query(self, client: Cleanlab) -> None: response = client.projects.knowledge.with_raw_response.query( project_id=0, question="question", @@ -298,7 +298,7 @@ def test_raw_response_query(self, client: CleanlabCodex) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - def test_streaming_response_query(self, client: CleanlabCodex) -> None: + def test_streaming_response_query(self, client: Cleanlab) -> None: with client.projects.knowledge.with_streaming_response.query( project_id=0, question="question", @@ -316,7 +316,7 @@ class TestAsyncKnowledge: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_create(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.create( project_id=0, question="question", @@ -324,7 +324,7 @@ async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.create( project_id=0, question="question", @@ -333,7 +333,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCo assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.knowledge.with_raw_response.create( project_id=0, question="question", @@ -345,7 +345,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.knowledge.with_streaming_response.create( project_id=0, question="question", @@ -359,7 +359,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -367,7 +367,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.knowledge.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -379,7 +379,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.knowledge.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -393,7 +393,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.retrieve( entry_id="", @@ -401,7 +401,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> N ) @parametrize - async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_update(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -409,7 +409,7 @@ async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -419,7 +419,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCo assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.knowledge.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -431,7 +431,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.knowledge.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -445,7 +445,7 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_path_params_update(self, async_client: AsyncCleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.update( entry_id="", @@ -453,14 +453,14 @@ async def test_path_params_update(self, async_client: AsyncCleanlabCodex) -> Non ) @parametrize - async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.list( project_id=0, ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_list_with_all_params(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.list( project_id=0, answered_only=True, @@ -473,7 +473,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCleanlabCode assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.knowledge.with_raw_response.list( project_id=0, ) @@ -484,7 +484,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.knowledge.with_streaming_response.list( project_id=0, ) as response: @@ -497,7 +497,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) - assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_delete(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -505,7 +505,7 @@ async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: assert knowledge is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.knowledge.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -517,7 +517,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> No assert knowledge is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.knowledge.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -531,7 +531,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_path_params_delete(self, async_client: AsyncCleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.delete( entry_id="", @@ -539,7 +539,7 @@ async def test_path_params_delete(self, async_client: AsyncCleanlabCodex) -> Non ) @parametrize - async def test_method_add_question(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_add_question(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.add_question( project_id=0, question="question", @@ -547,7 +547,7 @@ async def test_method_add_question(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_add_question(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_add_question(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.knowledge.with_raw_response.add_question( project_id=0, question="question", @@ -559,7 +559,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCleanlabCodex) assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_add_question(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_add_question(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.knowledge.with_streaming_response.add_question( project_id=0, question="question", @@ -573,7 +573,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCleanlab assert cast(Any, response.is_closed) is True @parametrize - async def test_method_query(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_query(self, async_client: AsyncCleanlab) -> None: knowledge = await async_client.projects.knowledge.query( project_id=0, question="question", @@ -581,7 +581,7 @@ async def test_method_query(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - async def test_raw_response_query(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_query(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.knowledge.with_raw_response.query( project_id=0, question="question", @@ -593,7 +593,7 @@ async def test_raw_response_query(self, async_client: AsyncCleanlabCodex) -> Non assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - async def test_streaming_response_query(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_query(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.knowledge.with_streaming_response.query( project_id=0, question="question", diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py index 268c45e2..98e6c91e 100644 --- a/tests/api_resources/test_health.py +++ b/tests/api_resources/test_health.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from codex.types import HealthCheckResponse from tests.utils import assert_matches_type @@ -18,12 +18,12 @@ class TestHealth: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_check(self, client: CleanlabCodex) -> None: + def test_method_check(self, client: Cleanlab) -> None: health = client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_check(self, client: CleanlabCodex) -> None: + def test_raw_response_check(self, client: Cleanlab) -> None: response = client.health.with_raw_response.check() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_check(self, client: CleanlabCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_check(self, client: CleanlabCodex) -> None: + def test_streaming_response_check(self, client: Cleanlab) -> None: with client.health.with_streaming_response.check() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -43,12 +43,12 @@ def test_streaming_response_check(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_db(self, client: CleanlabCodex) -> None: + def test_method_db(self, client: Cleanlab) -> None: health = client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_db(self, client: CleanlabCodex) -> None: + def test_raw_response_db(self, client: Cleanlab) -> None: response = client.health.with_raw_response.db() assert response.is_closed is True @@ -57,7 +57,7 @@ def test_raw_response_db(self, client: CleanlabCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_db(self, client: CleanlabCodex) -> None: + def test_streaming_response_db(self, client: Cleanlab) -> None: with client.health.with_streaming_response.db() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -68,12 +68,12 @@ def test_streaming_response_db(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_weaviate(self, client: CleanlabCodex) -> None: + def test_method_weaviate(self, client: Cleanlab) -> None: health = client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_weaviate(self, client: CleanlabCodex) -> None: + def test_raw_response_weaviate(self, client: Cleanlab) -> None: response = client.health.with_raw_response.weaviate() assert response.is_closed is True @@ -82,7 +82,7 @@ def test_raw_response_weaviate(self, client: CleanlabCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_weaviate(self, client: CleanlabCodex) -> None: + def test_streaming_response_weaviate(self, client: Cleanlab) -> None: with client.health.with_streaming_response.weaviate() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -97,12 +97,12 @@ class TestAsyncHealth: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_check(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_check(self, async_client: AsyncCleanlab) -> None: health = await async_client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_check(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_check(self, async_client: AsyncCleanlab) -> None: response = await async_client.health.with_raw_response.check() assert response.is_closed is True @@ -111,7 +111,7 @@ async def test_raw_response_check(self, async_client: AsyncCleanlabCodex) -> Non assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_check(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_check(self, async_client: AsyncCleanlab) -> None: async with async_client.health.with_streaming_response.check() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -122,12 +122,12 @@ async def test_streaming_response_check(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_db(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_db(self, async_client: AsyncCleanlab) -> None: health = await async_client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_db(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_db(self, async_client: AsyncCleanlab) -> None: response = await async_client.health.with_raw_response.db() assert response.is_closed is True @@ -136,7 +136,7 @@ async def test_raw_response_db(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_db(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_db(self, async_client: AsyncCleanlab) -> None: async with async_client.health.with_streaming_response.db() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -147,12 +147,12 @@ async def test_streaming_response_db(self, async_client: AsyncCleanlabCodex) -> assert cast(Any, response.is_closed) is True @parametrize - async def test_method_weaviate(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_weaviate(self, async_client: AsyncCleanlab) -> None: health = await async_client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_weaviate(self, async_client: AsyncCleanlab) -> None: response = await async_client.health.with_raw_response.weaviate() assert response.is_closed is True @@ -161,7 +161,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCleanlabCodex) -> assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_weaviate(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_weaviate(self, async_client: AsyncCleanlab) -> None: async with async_client.health.with_streaming_response.weaviate() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index bfca62d9..fa17b24b 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from codex.types import OrganizationSchemaPublic from tests.utils import assert_matches_type @@ -18,14 +18,14 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: CleanlabCodex) -> None: + def test_method_retrieve(self, client: Cleanlab) -> None: organization = client.organizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: + def test_raw_response_retrieve(self, client: Cleanlab) -> None: response = client.organizations.with_raw_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -36,7 +36,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: + def test_streaming_response_retrieve(self, client: Cleanlab) -> None: with client.organizations.with_streaming_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -49,7 +49,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_retrieve(self, client: CleanlabCodex) -> None: + def test_path_params_retrieve(self, client: Cleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.with_raw_response.retrieve( "", @@ -60,14 +60,14 @@ class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: organization = await async_client.organizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: response = await async_client.organizations.with_raw_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -78,7 +78,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: async with async_client.organizations.with_streaming_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -91,7 +91,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.with_raw_response.retrieve( "", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 64dbec49..c516e727 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from codex.types import ( ProjectListResponse, ProjectReturnSchema, @@ -21,7 +21,7 @@ class TestProjects: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: CleanlabCodex) -> None: + def test_method_create(self, client: Cleanlab) -> None: project = client.projects.create( config={}, name="name", @@ -30,7 +30,7 @@ def test_method_create(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: + def test_method_create_with_all_params(self, client: Cleanlab) -> None: project = client.projects.create( config={"max_distance": 0}, name="name", @@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_create(self, client: CleanlabCodex) -> None: + def test_raw_response_create(self, client: Cleanlab) -> None: response = client.projects.with_raw_response.create( config={}, name="name", @@ -53,7 +53,7 @@ def test_raw_response_create(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_create(self, client: CleanlabCodex) -> None: + def test_streaming_response_create(self, client: Cleanlab) -> None: with client.projects.with_streaming_response.create( config={}, name="name", @@ -68,14 +68,14 @@ def test_streaming_response_create(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: CleanlabCodex) -> None: + def test_method_retrieve(self, client: Cleanlab) -> None: project = client.projects.retrieve( 0, ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: + def test_raw_response_retrieve(self, client: Cleanlab) -> None: response = client.projects.with_raw_response.retrieve( 0, ) @@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: + def test_streaming_response_retrieve(self, client: Cleanlab) -> None: with client.projects.with_streaming_response.retrieve( 0, ) as response: @@ -99,7 +99,7 @@ def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_update(self, client: CleanlabCodex) -> None: + def test_method_update(self, client: Cleanlab) -> None: project = client.projects.update( project_id=0, config={}, @@ -108,7 +108,7 @@ def test_method_update(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: + def test_method_update_with_all_params(self, client: Cleanlab) -> None: project = client.projects.update( project_id=0, config={"max_distance": 0}, @@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_update(self, client: CleanlabCodex) -> None: + def test_raw_response_update(self, client: Cleanlab) -> None: response = client.projects.with_raw_response.update( project_id=0, config={}, @@ -131,7 +131,7 @@ def test_raw_response_update(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_update(self, client: CleanlabCodex) -> None: + def test_streaming_response_update(self, client: Cleanlab) -> None: with client.projects.with_streaming_response.update( project_id=0, config={}, @@ -146,14 +146,14 @@ def test_streaming_response_update(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_list(self, client: CleanlabCodex) -> None: + def test_method_list(self, client: Cleanlab) -> None: project = client.projects.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - def test_raw_response_list(self, client: CleanlabCodex) -> None: + def test_raw_response_list(self, client: Cleanlab) -> None: response = client.projects.with_raw_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -164,7 +164,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None: assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - def test_streaming_response_list(self, client: CleanlabCodex) -> None: + def test_streaming_response_list(self, client: Cleanlab) -> None: with client.projects.with_streaming_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -177,14 +177,14 @@ def test_streaming_response_list(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: CleanlabCodex) -> None: + def test_method_delete(self, client: Cleanlab) -> None: project = client.projects.delete( 0, ) assert project is None @parametrize - def test_raw_response_delete(self, client: CleanlabCodex) -> None: + def test_raw_response_delete(self, client: Cleanlab) -> None: response = client.projects.with_raw_response.delete( 0, ) @@ -195,7 +195,7 @@ def test_raw_response_delete(self, client: CleanlabCodex) -> None: assert project is None @parametrize - def test_streaming_response_delete(self, client: CleanlabCodex) -> None: + def test_streaming_response_delete(self, client: Cleanlab) -> None: with client.projects.with_streaming_response.delete( 0, ) as response: @@ -208,14 +208,14 @@ def test_streaming_response_delete(self, client: CleanlabCodex) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_export(self, client: CleanlabCodex) -> None: + def test_method_export(self, client: Cleanlab) -> None: project = client.projects.export( 0, ) assert_matches_type(object, project, path=["response"]) @parametrize - def test_raw_response_export(self, client: CleanlabCodex) -> None: + def test_raw_response_export(self, client: Cleanlab) -> None: response = client.projects.with_raw_response.export( 0, ) @@ -226,7 +226,7 @@ def test_raw_response_export(self, client: CleanlabCodex) -> None: assert_matches_type(object, project, path=["response"]) @parametrize - def test_streaming_response_export(self, client: CleanlabCodex) -> None: + def test_streaming_response_export(self, client: Cleanlab) -> None: with client.projects.with_streaming_response.export( 0, ) as response: @@ -243,7 +243,7 @@ class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_create(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.create( config={}, name="name", @@ -252,7 +252,7 @@ async def test_method_create(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.create( config={"max_distance": 0}, name="name", @@ -262,7 +262,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlabCo assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.with_raw_response.create( config={}, name="name", @@ -275,7 +275,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.with_streaming_response.create( config={}, name="name", @@ -290,14 +290,14 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.retrieve( 0, ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.with_raw_response.retrieve( 0, ) @@ -308,7 +308,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.with_streaming_response.retrieve( 0, ) as response: @@ -321,7 +321,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCode assert cast(Any, response.is_closed) is True @parametrize - async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_update(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.update( project_id=0, config={}, @@ -330,7 +330,7 @@ async def test_method_update(self, async_client: AsyncCleanlabCodex) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.update( project_id=0, config={"max_distance": 0}, @@ -340,7 +340,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlabCo assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.with_raw_response.update( project_id=0, config={}, @@ -353,7 +353,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.with_streaming_response.update( project_id=0, config={}, @@ -368,14 +368,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.with_raw_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -386,7 +386,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.with_streaming_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -399,14 +399,14 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) - assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_delete(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.delete( 0, ) assert project is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.with_raw_response.delete( 0, ) @@ -417,7 +417,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlabCodex) -> No assert project is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.with_streaming_response.delete( 0, ) as response: @@ -430,14 +430,14 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlabCodex) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_export(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_export(self, async_client: AsyncCleanlab) -> None: project = await async_client.projects.export( 0, ) assert_matches_type(object, project, path=["response"]) @parametrize - async def test_raw_response_export(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_export(self, async_client: AsyncCleanlab) -> None: response = await async_client.projects.with_raw_response.export( 0, ) @@ -448,7 +448,7 @@ async def test_raw_response_export(self, async_client: AsyncCleanlabCodex) -> No assert_matches_type(object, project, path=["response"]) @parametrize - async def test_streaming_response_export(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_export(self, async_client: AsyncCleanlab) -> None: async with async_client.projects.with_streaming_response.export( 0, ) as response: diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index af5fb8b7..36a68194 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from tests.utils import assert_matches_type from codex.types.users import UserSchema @@ -18,12 +18,12 @@ class TestAPIKey: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_refresh(self, client: CleanlabCodex) -> None: + def test_method_refresh(self, client: Cleanlab) -> None: api_key = client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - def test_raw_response_refresh(self, client: CleanlabCodex) -> None: + def test_raw_response_refresh(self, client: Cleanlab) -> None: response = client.users.myself.api_key.with_raw_response.refresh() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_refresh(self, client: CleanlabCodex) -> None: assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - def test_streaming_response_refresh(self, client: CleanlabCodex) -> None: + def test_streaming_response_refresh(self, client: Cleanlab) -> None: with client.users.myself.api_key.with_streaming_response.refresh() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncAPIKey: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_refresh(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_refresh(self, async_client: AsyncCleanlab) -> None: api_key = await async_client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - async def test_raw_response_refresh(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_refresh(self, async_client: AsyncCleanlab) -> None: response = await async_client.users.myself.api_key.with_raw_response.refresh() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCleanlabCodex) -> N assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - async def test_streaming_response_refresh(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_refresh(self, async_client: AsyncCleanlab) -> None: async with async_client.users.myself.api_key.with_streaming_response.refresh() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py index 15fd00f8..d50c4742 100644 --- a/tests/api_resources/users/myself/test_organizations.py +++ b/tests/api_resources/users/myself/test_organizations.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from tests.utils import assert_matches_type from codex.types.users.myself import UserOrganizationsSchema @@ -18,12 +18,12 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_list(self, client: CleanlabCodex) -> None: + def test_method_list(self, client: Cleanlab) -> None: organization = client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - def test_raw_response_list(self, client: CleanlabCodex) -> None: + def test_raw_response_list(self, client: Cleanlab) -> None: response = client.users.myself.organizations.with_raw_response.list() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_list(self, client: CleanlabCodex) -> None: assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - def test_streaming_response_list(self, client: CleanlabCodex) -> None: + def test_streaming_response_list(self, client: Cleanlab) -> None: with client.users.myself.organizations.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_list(self, async_client: AsyncCleanlab) -> None: organization = await async_client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: response = await async_client.users.myself.organizations.with_raw_response.list() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlabCodex) -> None assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: async with async_client.users.myself.organizations.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index de484616..f90be5c0 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -7,7 +7,7 @@ import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from tests.utils import assert_matches_type from codex.types.users import UserSchemaPublic @@ -18,12 +18,12 @@ class TestMyself: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: CleanlabCodex) -> None: + def test_method_retrieve(self, client: Cleanlab) -> None: myself = client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: + def test_raw_response_retrieve(self, client: Cleanlab) -> None: response = client.users.myself.with_raw_response.retrieve() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_retrieve(self, client: CleanlabCodex) -> None: assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: CleanlabCodex) -> None: + def test_streaming_response_retrieve(self, client: Cleanlab) -> None: with client.users.myself.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncMyself: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: myself = await async_client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: response = await async_client.users.myself.with_raw_response.retrieve() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlabCodex) -> assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlabCodex) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: async with async_client.users.myself.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/conftest.py b/tests/conftest.py index 7501cec9..40cbc004 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import pytest from pytest_asyncio import is_async_test -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest @@ -30,20 +30,20 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: @pytest.fixture(scope="session") -def client(request: FixtureRequest) -> Iterator[CleanlabCodex]: +def client(request: FixtureRequest) -> Iterator[Cleanlab]: strict = getattr(request, "param", True) if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with CleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client: + with Cleanlab(base_url=base_url, _strict_response_validation=strict) as client: yield client @pytest.fixture(scope="session") -async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlabCodex]: +async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlab]: strict = getattr(request, "param", True) if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - async with AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=strict) as client: + async with AsyncCleanlab(base_url=base_url, _strict_response_validation=strict) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 485d19e0..b9a2b4b9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -20,7 +20,7 @@ from respx import MockRouter from pydantic import ValidationError -from codex import CleanlabCodex, AsyncCleanlabCodex, APIResponseValidationError +from codex import Cleanlab, AsyncCleanlab, APIResponseValidationError from codex._types import Omit from codex._models import BaseModel, FinalRequestOptions from codex._constants import RAW_RESPONSE_HEADER @@ -42,7 +42,7 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 -def _get_open_connections(client: CleanlabCodex | AsyncCleanlabCodex) -> int: +def _get_open_connections(client: Cleanlab | AsyncCleanlab) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -50,8 +50,8 @@ def _get_open_connections(client: CleanlabCodex | AsyncCleanlabCodex) -> int: return len(pool._requests) -class TestCleanlabCodex: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) +class TestCleanlab: + client = Cleanlab(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -94,7 +94,7 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -126,7 +126,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -249,7 +249,7 @@ def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -258,7 +258,7 @@ def test_client_timeout_option(self) -> None: 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: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -266,7 +266,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -274,7 +274,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -283,15 +283,15 @@ def test_http_client_timeout_option(self) -> None: async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: - CleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) + Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) def test_default_headers_option(self) -> None: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = CleanlabCodex( + client2 = Cleanlab( base_url=base_url, _strict_response_validation=True, default_headers={ @@ -304,9 +304,7 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = CleanlabCodex( - base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"} - ) + client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -418,7 +416,7 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, client: CleanlabCodex) -> None: + def test_multipart_repeating_array(self, client: Cleanlab) -> None: request = client._build_request( FinalRequestOptions.construct( method="get", @@ -505,7 +503,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = CleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True) + client = Cleanlab(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -513,23 +511,23 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" def test_base_url_env(self) -> None: - with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): - client = CleanlabCodex(_strict_response_validation=True) + with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): + client = Cleanlab(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness - with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): + with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - CleanlabCodex(_strict_response_validation=True, environment="production") + Cleanlab(_strict_response_validation=True, environment="production") - client = CleanlabCodex(base_url=None, _strict_response_validation=True, environment="production") + client = Cleanlab(base_url=None, _strict_response_validation=True, environment="production") assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", [ - CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - CleanlabCodex( + Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Cleanlab( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -537,7 +535,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None: + def test_base_url_trailing_slash(self, client: Cleanlab) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -550,8 +548,8 @@ def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None: @pytest.mark.parametrize( "client", [ - CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - CleanlabCodex( + Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Cleanlab( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -559,7 +557,7 @@ def test_base_url_trailing_slash(self, client: CleanlabCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None: + def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -572,8 +570,8 @@ def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None: @pytest.mark.parametrize( "client", [ - CleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - CleanlabCodex( + Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Cleanlab( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -581,7 +579,7 @@ def test_base_url_no_trailing_slash(self, client: CleanlabCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: CleanlabCodex) -> None: + def test_absolute_request_url(self, client: Cleanlab) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -592,7 +590,7 @@ def test_absolute_request_url(self, client: CleanlabCodex) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) + client = Cleanlab(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -603,7 +601,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) + client = Cleanlab(base_url=base_url, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -624,7 +622,7 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - CleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + Cleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -633,12 +631,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) + strict_client = Cleanlab(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = CleanlabCodex(base_url=base_url, _strict_response_validation=False) + client = Cleanlab(base_url=base_url, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -666,7 +664,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = CleanlabCodex(base_url=base_url, _strict_response_validation=True) + client = Cleanlab(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -709,7 +707,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non @pytest.mark.parametrize("failure_mode", ["status", "exception"]) def test_retries_taken( self, - client: CleanlabCodex, + client: Cleanlab, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -740,7 +738,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_omit_retry_count_header( - self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter + self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) @@ -768,7 +766,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_overwrite_retry_count_header( - self, client: CleanlabCodex, failures_before_success: int, respx_mock: MockRouter + self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) @@ -793,8 +791,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" -class TestAsyncCleanlabCodex: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) +class TestAsyncCleanlab: + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -839,9 +837,7 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = AsyncCleanlabCodex( - base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"} - ) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -873,7 +869,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -996,7 +992,7 @@ async def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) async def test_client_timeout_option(self) -> None: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1005,7 +1001,7 @@ async def test_client_timeout_option(self) -> None: 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: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1013,7 +1009,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1021,7 +1017,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1030,19 +1026,15 @@ async def test_http_client_timeout_option(self) -> None: def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: - AsyncCleanlabCodex( - base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client) - ) + AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) def test_default_headers_option(self) -> None: - client = AsyncCleanlabCodex( - base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"} - ) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncCleanlabCodex( + client2 = AsyncCleanlab( base_url=base_url, _strict_response_validation=True, default_headers={ @@ -1055,7 +1047,7 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = AsyncCleanlabCodex( + client = AsyncCleanlab( base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1169,7 +1161,7 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, async_client: AsyncCleanlabCodex) -> None: + def test_multipart_repeating_array(self, async_client: AsyncCleanlab) -> None: request = async_client._build_request( FinalRequestOptions.construct( method="get", @@ -1256,7 +1248,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = AsyncCleanlabCodex(base_url="https://example.com/from_init", _strict_response_validation=True) + client = AsyncCleanlab(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -1264,23 +1256,23 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" def test_base_url_env(self) -> None: - with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): - client = AsyncCleanlabCodex(_strict_response_validation=True) + with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): + client = AsyncCleanlab(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness - with update_env(CLEANLAB_CODEX_BASE_URL="http://localhost:5000/from/env"): + with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - AsyncCleanlabCodex(_strict_response_validation=True, environment="production") + AsyncCleanlab(_strict_response_validation=True, environment="production") - client = AsyncCleanlabCodex(base_url=None, _strict_response_validation=True, environment="production") + client = AsyncCleanlab(base_url=None, _strict_response_validation=True, environment="production") assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", [ - AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCleanlabCodex( + AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCleanlab( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1288,7 +1280,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None: + def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1301,8 +1293,8 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None: @pytest.mark.parametrize( "client", [ - AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCleanlabCodex( + AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCleanlab( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1310,7 +1302,7 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlabCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None: + def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1323,8 +1315,8 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None: @pytest.mark.parametrize( "client", [ - AsyncCleanlabCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCleanlabCodex( + AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCleanlab( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1332,7 +1324,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlabCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncCleanlabCodex) -> None: + def test_absolute_request_url(self, client: AsyncCleanlab) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1343,7 +1335,7 @@ def test_absolute_request_url(self, client: AsyncCleanlabCodex) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1355,7 +1347,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1377,7 +1369,7 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + AsyncCleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -1387,12 +1379,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) + strict_client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=False) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1421,7 +1413,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncCleanlabCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -1465,7 +1457,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, - async_client: AsyncCleanlabCodex, + async_client: AsyncCleanlab, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -1497,7 +1489,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio async def test_omit_retry_count_header( - self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) @@ -1526,7 +1518,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio async def test_overwrite_retry_count_header( - self, async_client: AsyncCleanlabCodex, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) diff --git a/tests/test_response.py b/tests/test_response.py index b722c155..55b100ce 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -6,7 +6,7 @@ import pytest import pydantic -from codex import BaseModel, CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, BaseModel, AsyncCleanlab from codex._response import ( APIResponse, BaseAPIResponse, @@ -56,7 +56,7 @@ def test_extract_response_type_binary_response() -> None: class PydanticModel(pydantic.BaseModel): ... -def test_response_parse_mismatched_basemodel(client: CleanlabCodex) -> None: +def test_response_parse_mismatched_basemodel(client: Cleanlab) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -74,7 +74,7 @@ def test_response_parse_mismatched_basemodel(client: CleanlabCodex) -> None: @pytest.mark.asyncio -async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlabCodex) -> None: +async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlab) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -91,7 +91,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncClea await response.parse(to=PydanticModel) -def test_response_parse_custom_stream(client: CleanlabCodex) -> None: +def test_response_parse_custom_stream(client: Cleanlab) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -106,7 +106,7 @@ def test_response_parse_custom_stream(client: CleanlabCodex) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_stream(async_client: AsyncCleanlabCodex) -> None: +async def test_async_response_parse_custom_stream(async_client: AsyncCleanlab) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -125,7 +125,7 @@ class CustomModel(BaseModel): bar: int -def test_response_parse_custom_model(client: CleanlabCodex) -> None: +def test_response_parse_custom_model(client: Cleanlab) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -141,7 +141,7 @@ def test_response_parse_custom_model(client: CleanlabCodex) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_model(async_client: AsyncCleanlabCodex) -> None: +async def test_async_response_parse_custom_model(async_client: AsyncCleanlab) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -156,7 +156,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncCleanlabCode assert obj.bar == 2 -def test_response_parse_annotated_type(client: CleanlabCodex) -> None: +def test_response_parse_annotated_type(client: Cleanlab) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -173,7 +173,7 @@ def test_response_parse_annotated_type(client: CleanlabCodex) -> None: assert obj.bar == 2 -async def test_async_response_parse_annotated_type(async_client: AsyncCleanlabCodex) -> None: +async def test_async_response_parse_annotated_type(async_client: AsyncCleanlab) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -201,7 +201,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncCleanlabCo ("FalSe", False), ], ) -def test_response_parse_bool(client: CleanlabCodex, content: str, expected: bool) -> None: +def test_response_parse_bool(client: Cleanlab, content: str, expected: bool) -> None: response = APIResponse( raw=httpx.Response(200, content=content), client=client, @@ -226,7 +226,7 @@ def test_response_parse_bool(client: CleanlabCodex, content: str, expected: bool ("FalSe", False), ], ) -async def test_async_response_parse_bool(client: AsyncCleanlabCodex, content: str, expected: bool) -> None: +async def test_async_response_parse_bool(client: AsyncCleanlab, content: str, expected: bool) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=content), client=client, @@ -245,7 +245,7 @@ class OtherModel(BaseModel): @pytest.mark.parametrize("client", [False], indirect=True) # loose validation -def test_response_parse_expect_model_union_non_json_content(client: CleanlabCodex) -> None: +def test_response_parse_expect_model_union_non_json_content(client: Cleanlab) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=client, @@ -262,7 +262,7 @@ def test_response_parse_expect_model_union_non_json_content(client: CleanlabCode @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: AsyncCleanlabCodex) -> None: +async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCleanlab) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=async_client, diff --git a/tests/test_streaming.py b/tests/test_streaming.py index b6b669a6..9d0b6456 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -5,13 +5,13 @@ import httpx import pytest -from codex import CleanlabCodex, AsyncCleanlabCodex +from codex import Cleanlab, AsyncCleanlab from codex._streaming import Stream, AsyncStream, ServerSentEvent @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_basic(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: +async def test_basic(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b"event: completion\n" yield b'data: {"foo":true}\n' @@ -28,7 +28,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_missing_event(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: +async def test_data_missing_event(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b'data: {"foo":true}\n' yield b"\n" @@ -44,7 +44,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_event_missing_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: +async def test_event_missing_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -60,7 +60,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: +async def test_multiple_events(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -82,7 +82,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events_with_data(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: +async def test_multiple_events_with_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo":true}\n' @@ -106,9 +106,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines_with_empty_line( - sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex -) -> None: +async def test_multiple_data_lines_with_empty_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -130,9 +128,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_json_escaped_double_new_line( - sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex -) -> None: +async def test_data_json_escaped_double_new_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo": "my long\\n\\ncontent"}' @@ -149,7 +145,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines(sync: bool, client: CleanlabCodex, async_client: AsyncCleanlabCodex) -> None: +async def test_multiple_data_lines(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -169,8 +165,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_special_new_line_character( sync: bool, - client: CleanlabCodex, - async_client: AsyncCleanlabCodex, + client: Cleanlab, + async_client: AsyncCleanlab, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":" culpa"}\n' @@ -200,8 +196,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_multi_byte_character_multiple_chunks( sync: bool, - client: CleanlabCodex, - async_client: AsyncCleanlabCodex, + client: Cleanlab, + async_client: AsyncCleanlab, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":"' @@ -241,8 +237,8 @@ def make_event_iterator( content: Iterator[bytes], *, sync: bool, - client: CleanlabCodex, - async_client: AsyncCleanlabCodex, + client: Cleanlab, + async_client: AsyncCleanlab, ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: if sync: return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() From d13a0082f0d691ae9c771595213d2f1d115c1266 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:31:15 +0000 Subject: [PATCH 006/320] feat(api): update org name --- LICENSE | 2 +- README.md | 46 +++--- SECURITY.md | 4 +- pyproject.toml | 4 +- src/codex/__init__.py | 12 +- src/codex/_client.py | 56 +++---- src/codex/_exceptions.py | 4 +- src/codex/_resource.py | 10 +- src/codex/_response.py | 4 +- src/codex/_streaming.py | 6 +- src/codex/_utils/_logs.py | 2 +- .../organizations/test_billing.py | 34 ++-- .../projects/test_access_keys.py | 82 ++++----- .../api_resources/projects/test_knowledge.py | 110 ++++++------ tests/api_resources/test_health.py | 38 ++--- tests/api_resources/test_organizations.py | 18 +- tests/api_resources/test_projects.py | 82 ++++----- .../users/myself/test_api_key.py | 14 +- .../users/myself/test_organizations.py | 14 +- tests/api_resources/users/test_myself.py | 14 +- tests/conftest.py | 10 +- tests/test_client.py | 156 +++++++++--------- tests/test_response.py | 26 +-- tests/test_streaming.py | 30 ++-- 24 files changed, 387 insertions(+), 391 deletions(-) diff --git a/LICENSE b/LICENSE index e575118f..f00d3a6a 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Cleanlab + Copyright 2025 Codex 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 b156eb36..f84291e1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Cleanlab Python API library +# Codex Python API library [![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/) -The Cleanlab Python library provides convenient access to the Cleanlab REST API from any Python 3.8+ +The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+ 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). @@ -27,9 +27,9 @@ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git The full API of this library can be found in [api.md](api.md). ```python -from codex import Cleanlab +from codex import Codex -client = Cleanlab( +client = Codex( # or 'production' | 'local'; defaults to "production". environment="staging", ) @@ -49,13 +49,13 @@ so that your Bearer Token is not stored in source control. ## Async usage -Simply import `AsyncCleanlab` instead of `Cleanlab` and use `await` with each API call: +Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call: ```python import asyncio -from codex import AsyncCleanlab +from codex import AsyncCodex -client = AsyncCleanlab( +client = AsyncCodex( # or 'production' | 'local'; defaults to "production". environment="staging", ) @@ -95,9 +95,9 @@ All errors inherit from `codex.APIError`. ```python import codex -from codex import Cleanlab +from codex import Codex -client = Cleanlab() +client = Codex() try: client.projects.create( @@ -138,10 +138,10 @@ Connection errors (for example, due to a network connectivity problem), 408 Requ You can use the `max_retries` option to configure or disable retry settings: ```python -from codex import Cleanlab +from codex import Codex # Configure the default for all requests: -client = Cleanlab( +client = Codex( # default is 2 max_retries=0, ) @@ -160,16 +160,16 @@ By default requests time out after 1 minute. You can configure this with a `time which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: ```python -from codex import Cleanlab +from codex import Codex # Configure the default for all requests: -client = Cleanlab( +client = Codex( # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: -client = Cleanlab( +client = Codex( timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) @@ -191,10 +191,10 @@ 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 `CLEANLAB_LOG` to `info`. +You can enable logging by setting the environment variable `CODEX_LOG` to `info`. ```shell -$ export CLEANLAB_LOG=info +$ export CODEX_LOG=info ``` Or to `debug` for more verbose logging. @@ -216,9 +216,9 @@ if response.my_field is None: The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., ```py -from codex import Cleanlab +from codex import Codex -client = Cleanlab() +client = Codex() response = client.projects.with_raw_response.create( config={}, name="name", @@ -298,10 +298,10 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c ```python import httpx -from codex import Cleanlab, DefaultHttpxClient +from codex import Codex, DefaultHttpxClient -client = Cleanlab( - # Or use the `CLEANLAB_BASE_URL` env var +client = Codex( + # Or use the `CODEX_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( proxy="http://my.test.proxy.example.com", @@ -321,9 +321,9 @@ 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 codex import Cleanlab +from codex import Codex -with Cleanlab() as client: +with Codex() as client: # make requests here ... diff --git a/SECURITY.md b/SECURITY.md index 2ca30344..fcad344e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,9 +16,9 @@ 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 Cleanlab please follow the respective company's security reporting guidelines. +or products provided by Codex please follow the respective company's security reporting guidelines. -### Cleanlab Terms and Policies +### Codex Terms and Policies Please contact support@cleanlab.ai for any questions or concerns regarding security of our services. diff --git a/pyproject.toml b/pyproject.toml index 5ba1074c..3fba9e70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "codex" version = "0.0.1-alpha.0" -description = "The official Python library for the Cleanlab API" +description = "The official Python library for the Codex API" dynamic = ["readme"] license = "Apache-2.0" authors = [ -{ name = "Cleanlab", email = "support@cleanlab.ai" }, +{ name = "Codex", email = "support@cleanlab.ai" }, ] dependencies = [ "httpx>=0.23.0, <1", diff --git a/src/codex/__init__.py b/src/codex/__init__.py index e3c0220d..d6dffe2c 100644 --- a/src/codex/__init__.py +++ b/src/codex/__init__.py @@ -5,14 +5,14 @@ from ._utils import file_from_path from ._client import ( ENVIRONMENTS, + Codex, Client, Stream, Timeout, - Cleanlab, Transport, + AsyncCodex, AsyncClient, AsyncStream, - AsyncCleanlab, RequestOptions, ) from ._models import BaseModel @@ -21,7 +21,7 @@ from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS from ._exceptions import ( APIError, - CleanlabError, + CodexError, ConflictError, NotFoundError, APIStatusError, @@ -48,7 +48,7 @@ "NotGiven", "NOT_GIVEN", "Omit", - "CleanlabError", + "CodexError", "APIError", "APIStatusError", "APITimeoutError", @@ -68,8 +68,8 @@ "AsyncClient", "Stream", "AsyncStream", - "Cleanlab", - "AsyncCleanlab", + "Codex", + "AsyncCodex", "ENVIRONMENTS", "file_from_path", "BaseModel", diff --git a/src/codex/_client.py b/src/codex/_client.py index 0d3b6b8d..d9e44320 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -43,8 +43,8 @@ "Transport", "ProxiesTypes", "RequestOptions", - "Cleanlab", - "AsyncCleanlab", + "Codex", + "AsyncCodex", "Client", "AsyncClient", ] @@ -56,13 +56,13 @@ } -class Cleanlab(SyncAPIClient): +class Codex(SyncAPIClient): health: health.HealthResource organizations: organizations.OrganizationsResource users: users.UsersResource projects: projects.ProjectsResource - with_raw_response: CleanlabWithRawResponse - with_streaming_response: CleanlabWithStreamedResponse + with_raw_response: CodexWithRawResponse + with_streaming_response: CodexWithStreamedResponse # client options bearer_token: str | None @@ -97,7 +97,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous Cleanlab client instance. + """Construct a new synchronous Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `bearer_token` from `BEARER_TOKEN` @@ -118,14 +118,14 @@ def __init__( self._environment = environment - base_url_env = os.environ.get("CLEANLAB_BASE_URL") + base_url_env = os.environ.get("CODEX_BASE_URL") if is_given(base_url) and base_url is not None: # cast required because mypy doesn't understand the type narrowing base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] elif is_given(environment): if base_url_env and base_url is not None: raise ValueError( - "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", ) try: @@ -157,8 +157,8 @@ def __init__( self.organizations = organizations.OrganizationsResource(self) self.users = users.UsersResource(self) self.projects = projects.ProjectsResource(self) - self.with_raw_response = CleanlabWithRawResponse(self) - self.with_streaming_response = CleanlabWithStreamedResponse(self) + self.with_raw_response = CodexWithRawResponse(self) + self.with_streaming_response = CodexWithStreamedResponse(self) @property @override @@ -318,13 +318,13 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class AsyncCleanlab(AsyncAPIClient): +class AsyncCodex(AsyncAPIClient): health: health.AsyncHealthResource organizations: organizations.AsyncOrganizationsResource users: users.AsyncUsersResource projects: projects.AsyncProjectsResource - with_raw_response: AsyncCleanlabWithRawResponse - with_streaming_response: AsyncCleanlabWithStreamedResponse + with_raw_response: AsyncCodexWithRawResponse + with_streaming_response: AsyncCodexWithStreamedResponse # client options bearer_token: str | None @@ -359,7 +359,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async Cleanlab client instance. + """Construct a new async Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `bearer_token` from `BEARER_TOKEN` @@ -380,14 +380,14 @@ def __init__( self._environment = environment - base_url_env = os.environ.get("CLEANLAB_BASE_URL") + base_url_env = os.environ.get("CODEX_BASE_URL") if is_given(base_url) and base_url is not None: # cast required because mypy doesn't understand the type narrowing base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] elif is_given(environment): if base_url_env and base_url is not None: raise ValueError( - "Ambiguous URL; The `CLEANLAB_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", + "Ambiguous URL; The `CODEX_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", ) try: @@ -419,8 +419,8 @@ def __init__( self.organizations = organizations.AsyncOrganizationsResource(self) self.users = users.AsyncUsersResource(self) self.projects = projects.AsyncProjectsResource(self) - self.with_raw_response = AsyncCleanlabWithRawResponse(self) - self.with_streaming_response = AsyncCleanlabWithStreamedResponse(self) + self.with_raw_response = AsyncCodexWithRawResponse(self) + self.with_streaming_response = AsyncCodexWithStreamedResponse(self) @property @override @@ -580,38 +580,38 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class CleanlabWithRawResponse: - def __init__(self, client: Cleanlab) -> None: +class CodexWithRawResponse: + def __init__(self, client: Codex) -> None: self.health = health.HealthResourceWithRawResponse(client.health) self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations) self.users = users.UsersResourceWithRawResponse(client.users) self.projects = projects.ProjectsResourceWithRawResponse(client.projects) -class AsyncCleanlabWithRawResponse: - def __init__(self, client: AsyncCleanlab) -> None: +class AsyncCodexWithRawResponse: + def __init__(self, client: AsyncCodex) -> None: self.health = health.AsyncHealthResourceWithRawResponse(client.health) self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations) self.users = users.AsyncUsersResourceWithRawResponse(client.users) self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) -class CleanlabWithStreamedResponse: - def __init__(self, client: Cleanlab) -> None: +class CodexWithStreamedResponse: + def __init__(self, client: Codex) -> None: self.health = health.HealthResourceWithStreamingResponse(client.health) self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.UsersResourceWithStreamingResponse(client.users) self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) -class AsyncCleanlabWithStreamedResponse: - def __init__(self, client: AsyncCleanlab) -> None: +class AsyncCodexWithStreamedResponse: + def __init__(self, client: AsyncCodex) -> None: self.health = health.AsyncHealthResourceWithStreamingResponse(client.health) self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.AsyncUsersResourceWithStreamingResponse(client.users) self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) -Client = Cleanlab +Client = Codex -AsyncClient = AsyncCleanlab +AsyncClient = AsyncCodex diff --git a/src/codex/_exceptions.py b/src/codex/_exceptions.py index db0f4b62..90164ee4 100644 --- a/src/codex/_exceptions.py +++ b/src/codex/_exceptions.py @@ -18,11 +18,11 @@ ] -class CleanlabError(Exception): +class CodexError(Exception): pass -class APIError(CleanlabError): +class APIError(CodexError): message: str request: httpx.Request diff --git a/src/codex/_resource.py b/src/codex/_resource.py index 1bf30f6b..7ba43eeb 100644 --- a/src/codex/_resource.py +++ b/src/codex/_resource.py @@ -8,13 +8,13 @@ import anyio if TYPE_CHECKING: - from ._client import Cleanlab, AsyncCleanlab + from ._client import Codex, AsyncCodex class SyncAPIResource: - _client: Cleanlab + _client: Codex - def __init__(self, client: Cleanlab) -> None: + def __init__(self, client: Codex) -> None: self._client = client self._get = client.get self._post = client.post @@ -28,9 +28,9 @@ def _sleep(self, seconds: float) -> None: class AsyncAPIResource: - _client: AsyncCleanlab + _client: AsyncCodex - def __init__(self, client: AsyncCleanlab) -> None: + def __init__(self, client: AsyncCodex) -> None: self._client = client self._get = client.get self._post = client.post diff --git a/src/codex/_response.py b/src/codex/_response.py index 4faa64ef..b63926f4 100644 --- a/src/codex/_response.py +++ b/src/codex/_response.py @@ -29,7 +29,7 @@ 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 -from ._exceptions import CleanlabError, APIResponseValidationError +from ._exceptions import CodexError, APIResponseValidationError if TYPE_CHECKING: from ._models import FinalRequestOptions @@ -554,7 +554,7 @@ def __init__(self) -> None: ) -class StreamAlreadyConsumed(CleanlabError): +class StreamAlreadyConsumed(CodexError): """ Attempted to read or stream content, but the content has already been streamed. diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py index e545fc0f..3af102ce 100644 --- a/src/codex/_streaming.py +++ b/src/codex/_streaming.py @@ -12,7 +12,7 @@ from ._utils import extract_type_var_from_base if TYPE_CHECKING: - from ._client import Cleanlab, AsyncCleanlab + from ._client import Codex, AsyncCodex _T = TypeVar("_T") @@ -30,7 +30,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: Cleanlab, + client: Codex, ) -> None: self.response = response self._cast_to = cast_to @@ -93,7 +93,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: AsyncCleanlab, + client: AsyncCodex, ) -> None: self.response = response self._cast_to = cast_to diff --git a/src/codex/_utils/_logs.py b/src/codex/_utils/_logs.py index 58944f5f..80932c4f 100644 --- a/src/codex/_utils/_logs.py +++ b/src/codex/_utils/_logs.py @@ -14,7 +14,7 @@ def _basic_config() -> None: def setup_logging() -> None: - env = os.environ.get("CLEANLAB_LOG") + env = os.environ.get("CODEX_LOG") if env == "debug": _basic_config() logger.setLevel(logging.DEBUG) diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py index c1530b3b..ccb7dc8a 100644 --- a/tests/api_resources/organizations/test_billing.py +++ b/tests/api_resources/organizations/test_billing.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex.types.organizations import OrganizationBillingUsageSchema, OrganizationBillingInvoicesSchema @@ -18,14 +18,14 @@ class TestBilling: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_invoices(self, client: Cleanlab) -> None: + def test_method_invoices(self, client: Codex) -> None: billing = client.organizations.billing.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - def test_raw_response_invoices(self, client: Cleanlab) -> None: + def test_raw_response_invoices(self, client: Codex) -> None: response = client.organizations.billing.with_raw_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -36,7 +36,7 @@ def test_raw_response_invoices(self, client: Cleanlab) -> None: assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - def test_streaming_response_invoices(self, client: Cleanlab) -> None: + def test_streaming_response_invoices(self, client: Codex) -> None: with client.organizations.billing.with_streaming_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -49,21 +49,21 @@ def test_streaming_response_invoices(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_invoices(self, client: Cleanlab) -> None: + def test_path_params_invoices(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.billing.with_raw_response.invoices( "", ) @parametrize - def test_method_usage(self, client: Cleanlab) -> None: + def test_method_usage(self, client: Codex) -> None: billing = client.organizations.billing.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - def test_raw_response_usage(self, client: Cleanlab) -> None: + def test_raw_response_usage(self, client: Codex) -> None: response = client.organizations.billing.with_raw_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -74,7 +74,7 @@ def test_raw_response_usage(self, client: Cleanlab) -> None: assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - def test_streaming_response_usage(self, client: Cleanlab) -> None: + def test_streaming_response_usage(self, client: Codex) -> None: with client.organizations.billing.with_streaming_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -87,7 +87,7 @@ def test_streaming_response_usage(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_usage(self, client: Cleanlab) -> None: + def test_path_params_usage(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.billing.with_raw_response.usage( "", @@ -98,14 +98,14 @@ class TestAsyncBilling: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_invoices(self, async_client: AsyncCleanlab) -> None: + async def test_method_invoices(self, async_client: AsyncCodex) -> None: billing = await async_client.organizations.billing.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - async def test_raw_response_invoices(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.with_raw_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -116,7 +116,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCleanlab) -> None: assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) @parametrize - async def test_streaming_response_invoices(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.with_streaming_response.invoices( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -129,21 +129,21 @@ async def test_streaming_response_invoices(self, async_client: AsyncCleanlab) -> assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_invoices(self, async_client: AsyncCleanlab) -> None: + async def test_path_params_invoices(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.billing.with_raw_response.invoices( "", ) @parametrize - async def test_method_usage(self, async_client: AsyncCleanlab) -> None: + async def test_method_usage(self, async_client: AsyncCodex) -> None: billing = await async_client.organizations.billing.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - async def test_raw_response_usage(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.with_raw_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -154,7 +154,7 @@ async def test_raw_response_usage(self, async_client: AsyncCleanlab) -> None: assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) @parametrize - async def test_streaming_response_usage(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.with_streaming_response.usage( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -167,7 +167,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCleanlab) -> No assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_usage(self, async_client: AsyncCleanlab) -> None: + async def test_path_params_usage(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.billing.with_raw_response.usage( "", diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index 26f984b0..4fa1636f 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex._utils import parse_datetime from codex.types.projects import ( @@ -22,7 +22,7 @@ class TestAccessKeys: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: Cleanlab) -> None: + def test_method_create(self, client: Codex) -> None: access_key = client.projects.access_keys.create( project_id=0, name="name", @@ -30,7 +30,7 @@ def test_method_create(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: Cleanlab) -> None: + def test_method_create_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.create( project_id=0, name="name", @@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_create(self, client: Cleanlab) -> None: + def test_raw_response_create(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.create( project_id=0, name="name", @@ -52,7 +52,7 @@ def test_raw_response_create(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_create(self, client: Cleanlab) -> None: + def test_streaming_response_create(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.create( project_id=0, name="name", @@ -66,7 +66,7 @@ def test_streaming_response_create(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: Cleanlab) -> None: + def test_method_retrieve(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve( access_key_id=0, project_id=0, @@ -74,7 +74,7 @@ def test_method_retrieve(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Cleanlab) -> None: + def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.retrieve( access_key_id=0, project_id=0, @@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Cleanlab) -> None: + def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.retrieve( access_key_id=0, project_id=0, @@ -100,7 +100,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_update(self, client: Cleanlab) -> None: + def test_method_update(self, client: Codex) -> None: access_key = client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -109,7 +109,7 @@ def test_method_update(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: Cleanlab) -> None: + def test_method_update_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -120,7 +120,7 @@ def test_method_update_with_all_params(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_raw_response_update(self, client: Cleanlab) -> None: + def test_raw_response_update(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.update( access_key_id=0, project_id=0, @@ -133,7 +133,7 @@ def test_raw_response_update(self, client: Cleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - def test_streaming_response_update(self, client: Cleanlab) -> None: + def test_streaming_response_update(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.update( access_key_id=0, project_id=0, @@ -148,14 +148,14 @@ def test_streaming_response_update(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_list(self, client: Cleanlab) -> None: + def test_method_list(self, client: Codex) -> None: access_key = client.projects.access_keys.list( 0, ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - def test_raw_response_list(self, client: Cleanlab) -> None: + def test_raw_response_list(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.list( 0, ) @@ -166,7 +166,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None: assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Cleanlab) -> None: + def test_streaming_response_list(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.list( 0, ) as response: @@ -179,7 +179,7 @@ def test_streaming_response_list(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: Cleanlab) -> None: + def test_method_delete(self, client: Codex) -> None: access_key = client.projects.access_keys.delete( access_key_id=0, project_id=0, @@ -187,7 +187,7 @@ def test_method_delete(self, client: Cleanlab) -> None: assert access_key is None @parametrize - def test_raw_response_delete(self, client: Cleanlab) -> None: + def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.delete( access_key_id=0, project_id=0, @@ -199,7 +199,7 @@ def test_raw_response_delete(self, client: Cleanlab) -> None: assert access_key is None @parametrize - def test_streaming_response_delete(self, client: Cleanlab) -> None: + def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.delete( access_key_id=0, project_id=0, @@ -213,7 +213,7 @@ def test_streaming_response_delete(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_revoke(self, client: Cleanlab) -> None: + def test_method_revoke(self, client: Codex) -> None: access_key = client.projects.access_keys.revoke( access_key_id=0, project_id=0, @@ -221,7 +221,7 @@ def test_method_revoke(self, client: Cleanlab) -> None: assert access_key is None @parametrize - def test_raw_response_revoke(self, client: Cleanlab) -> None: + def test_raw_response_revoke(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.revoke( access_key_id=0, project_id=0, @@ -233,7 +233,7 @@ def test_raw_response_revoke(self, client: Cleanlab) -> None: assert access_key is None @parametrize - def test_streaming_response_revoke(self, client: Cleanlab) -> None: + def test_streaming_response_revoke(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.revoke( access_key_id=0, project_id=0, @@ -251,7 +251,7 @@ class TestAsyncAccessKeys: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCleanlab) -> None: + async def test_method_create(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( project_id=0, name="name", @@ -259,7 +259,7 @@ async def test_method_create(self, async_client: AsyncCleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( project_id=0, name="name", @@ -269,7 +269,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.create( project_id=0, name="name", @@ -281,7 +281,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.create( project_id=0, name="name", @@ -295,7 +295,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve( access_key_id=0, project_id=0, @@ -303,7 +303,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve( access_key_id=0, project_id=0, @@ -315,7 +315,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve( access_key_id=0, project_id=0, @@ -329,7 +329,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> assert cast(Any, response.is_closed) is True @parametrize - async def test_method_update(self, async_client: AsyncCleanlab) -> None: + async def test_method_update(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -338,7 +338,7 @@ async def test_method_update(self, async_client: AsyncCleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( access_key_id=0, project_id=0, @@ -349,7 +349,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.update( access_key_id=0, project_id=0, @@ -362,7 +362,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: assert_matches_type(AccessKeySchema, access_key, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.update( access_key_id=0, project_id=0, @@ -377,14 +377,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_method_list(self, async_client: AsyncCleanlab) -> None: + async def test_method_list(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.list( 0, ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.list( 0, ) @@ -395,7 +395,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.list( 0, ) as response: @@ -408,7 +408,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> Non assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCleanlab) -> None: + async def test_method_delete(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.delete( access_key_id=0, project_id=0, @@ -416,7 +416,7 @@ async def test_method_delete(self, async_client: AsyncCleanlab) -> None: assert access_key is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.delete( access_key_id=0, project_id=0, @@ -428,7 +428,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: assert access_key is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.delete( access_key_id=0, project_id=0, @@ -442,7 +442,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_method_revoke(self, async_client: AsyncCleanlab) -> None: + async def test_method_revoke(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.revoke( access_key_id=0, project_id=0, @@ -450,7 +450,7 @@ async def test_method_revoke(self, async_client: AsyncCleanlab) -> None: assert access_key is None @parametrize - async def test_raw_response_revoke(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.revoke( access_key_id=0, project_id=0, @@ -462,7 +462,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCleanlab) -> None: assert access_key is None @parametrize - async def test_streaming_response_revoke(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.revoke( access_key_id=0, project_id=0, diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py index 9755583f..9052b62d 100644 --- a/tests/api_resources/projects/test_knowledge.py +++ b/tests/api_resources/projects/test_knowledge.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex.types.projects import ( Entry, @@ -21,7 +21,7 @@ class TestKnowledge: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: Cleanlab) -> None: + def test_method_create(self, client: Codex) -> None: knowledge = client.projects.knowledge.create( project_id=0, question="question", @@ -29,7 +29,7 @@ def test_method_create(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: Cleanlab) -> None: + def test_method_create_with_all_params(self, client: Codex) -> None: knowledge = client.projects.knowledge.create( project_id=0, question="question", @@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_create(self, client: Cleanlab) -> None: + def test_raw_response_create(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.create( project_id=0, question="question", @@ -50,7 +50,7 @@ def test_raw_response_create(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_create(self, client: Cleanlab) -> None: + def test_streaming_response_create(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.create( project_id=0, question="question", @@ -64,7 +64,7 @@ def test_streaming_response_create(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: Cleanlab) -> None: + def test_method_retrieve(self, client: Codex) -> None: knowledge = client.projects.knowledge.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -72,7 +72,7 @@ def test_method_retrieve(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Cleanlab) -> None: + def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -84,7 +84,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Cleanlab) -> None: + def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -98,7 +98,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_retrieve(self, client: Cleanlab) -> None: + def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.retrieve( entry_id="", @@ -106,7 +106,7 @@ def test_path_params_retrieve(self, client: Cleanlab) -> None: ) @parametrize - def test_method_update(self, client: Cleanlab) -> None: + def test_method_update(self, client: Codex) -> None: knowledge = client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -114,7 +114,7 @@ def test_method_update(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: Cleanlab) -> None: + def test_method_update_with_all_params(self, client: Codex) -> None: knowledge = client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -124,7 +124,7 @@ def test_method_update_with_all_params(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_update(self, client: Cleanlab) -> None: + def test_raw_response_update(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -136,7 +136,7 @@ def test_raw_response_update(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_update(self, client: Cleanlab) -> None: + def test_streaming_response_update(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -150,7 +150,7 @@ def test_streaming_response_update(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_update(self, client: Cleanlab) -> None: + def test_path_params_update(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.update( entry_id="", @@ -158,14 +158,14 @@ def test_path_params_update(self, client: Cleanlab) -> None: ) @parametrize - def test_method_list(self, client: Cleanlab) -> None: + def test_method_list(self, client: Codex) -> None: knowledge = client.projects.knowledge.list( project_id=0, ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_method_list_with_all_params(self, client: Cleanlab) -> None: + def test_method_list_with_all_params(self, client: Codex) -> None: knowledge = client.projects.knowledge.list( project_id=0, answered_only=True, @@ -178,7 +178,7 @@ def test_method_list_with_all_params(self, client: Cleanlab) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_raw_response_list(self, client: Cleanlab) -> None: + def test_raw_response_list(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.list( project_id=0, ) @@ -189,7 +189,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Cleanlab) -> None: + def test_streaming_response_list(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.list( project_id=0, ) as response: @@ -202,7 +202,7 @@ def test_streaming_response_list(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: Cleanlab) -> None: + def test_method_delete(self, client: Codex) -> None: knowledge = client.projects.knowledge.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -210,7 +210,7 @@ def test_method_delete(self, client: Cleanlab) -> None: assert knowledge is None @parametrize - def test_raw_response_delete(self, client: Cleanlab) -> None: + def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -222,7 +222,7 @@ def test_raw_response_delete(self, client: Cleanlab) -> None: assert knowledge is None @parametrize - def test_streaming_response_delete(self, client: Cleanlab) -> None: + def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_delete(self, client: Cleanlab) -> None: + def test_path_params_delete(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.knowledge.with_raw_response.delete( entry_id="", @@ -244,7 +244,7 @@ def test_path_params_delete(self, client: Cleanlab) -> None: ) @parametrize - def test_method_add_question(self, client: Cleanlab) -> None: + def test_method_add_question(self, client: Codex) -> None: knowledge = client.projects.knowledge.add_question( project_id=0, question="question", @@ -252,7 +252,7 @@ def test_method_add_question(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_raw_response_add_question(self, client: Cleanlab) -> None: + def test_raw_response_add_question(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.add_question( project_id=0, question="question", @@ -264,7 +264,7 @@ def test_raw_response_add_question(self, client: Cleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - def test_streaming_response_add_question(self, client: Cleanlab) -> None: + def test_streaming_response_add_question(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.add_question( project_id=0, question="question", @@ -278,7 +278,7 @@ def test_streaming_response_add_question(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_query(self, client: Cleanlab) -> None: + def test_method_query(self, client: Codex) -> None: knowledge = client.projects.knowledge.query( project_id=0, question="question", @@ -286,7 +286,7 @@ def test_method_query(self, client: Cleanlab) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - def test_raw_response_query(self, client: Cleanlab) -> None: + def test_raw_response_query(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.query( project_id=0, question="question", @@ -298,7 +298,7 @@ def test_raw_response_query(self, client: Cleanlab) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - def test_streaming_response_query(self, client: Cleanlab) -> None: + def test_streaming_response_query(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.query( project_id=0, question="question", @@ -316,7 +316,7 @@ class TestAsyncKnowledge: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCleanlab) -> None: + async def test_method_create(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.create( project_id=0, question="question", @@ -324,7 +324,7 @@ async def test_method_create(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.create( project_id=0, question="question", @@ -333,7 +333,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.create( project_id=0, question="question", @@ -345,7 +345,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.create( project_id=0, question="question", @@ -359,7 +359,7 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -367,7 +367,7 @@ async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -379,7 +379,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -393,7 +393,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.retrieve( entry_id="", @@ -401,7 +401,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None: ) @parametrize - async def test_method_update(self, async_client: AsyncCleanlab) -> None: + async def test_method_update(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -409,7 +409,7 @@ async def test_method_update(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -419,7 +419,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -431,7 +431,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -445,7 +445,7 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_update(self, async_client: AsyncCleanlab) -> None: + async def test_path_params_update(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.update( entry_id="", @@ -453,14 +453,14 @@ async def test_path_params_update(self, async_client: AsyncCleanlab) -> None: ) @parametrize - async def test_method_list(self, async_client: AsyncCleanlab) -> None: + async def test_method_list(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.list( project_id=0, ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncCleanlab) -> None: + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.list( project_id=0, answered_only=True, @@ -473,7 +473,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCleanlab) -> assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.list( project_id=0, ) @@ -484,7 +484,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.list( project_id=0, ) as response: @@ -497,7 +497,7 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> Non assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCleanlab) -> None: + async def test_method_delete(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -505,7 +505,7 @@ async def test_method_delete(self, async_client: AsyncCleanlab) -> None: assert knowledge is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -517,7 +517,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: assert knowledge is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id=0, @@ -531,7 +531,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_delete(self, async_client: AsyncCleanlab) -> None: + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.knowledge.with_raw_response.delete( entry_id="", @@ -539,7 +539,7 @@ async def test_path_params_delete(self, async_client: AsyncCleanlab) -> None: ) @parametrize - async def test_method_add_question(self, async_client: AsyncCleanlab) -> None: + async def test_method_add_question(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.add_question( project_id=0, question="question", @@ -547,7 +547,7 @@ async def test_method_add_question(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_raw_response_add_question(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.add_question( project_id=0, question="question", @@ -559,7 +559,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCleanlab) -> N assert_matches_type(Entry, knowledge, path=["response"]) @parametrize - async def test_streaming_response_add_question(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.add_question( project_id=0, question="question", @@ -573,7 +573,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCleanlab assert cast(Any, response.is_closed) is True @parametrize - async def test_method_query(self, async_client: AsyncCleanlab) -> None: + async def test_method_query(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.query( project_id=0, question="question", @@ -581,7 +581,7 @@ async def test_method_query(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - async def test_raw_response_query(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_query(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.query( project_id=0, question="question", @@ -593,7 +593,7 @@ async def test_raw_response_query(self, async_client: AsyncCleanlab) -> None: assert_matches_type(Optional[Entry], knowledge, path=["response"]) @parametrize - async def test_streaming_response_query(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.query( project_id=0, question="question", diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py index 98e6c91e..be4729ab 100644 --- a/tests/api_resources/test_health.py +++ b/tests/api_resources/test_health.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from codex.types import HealthCheckResponse from tests.utils import assert_matches_type @@ -18,12 +18,12 @@ class TestHealth: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_check(self, client: Cleanlab) -> None: + def test_method_check(self, client: Codex) -> None: health = client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_check(self, client: Cleanlab) -> None: + def test_raw_response_check(self, client: Codex) -> None: response = client.health.with_raw_response.check() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_check(self, client: Cleanlab) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_check(self, client: Cleanlab) -> None: + def test_streaming_response_check(self, client: Codex) -> None: with client.health.with_streaming_response.check() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -43,12 +43,12 @@ def test_streaming_response_check(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_db(self, client: Cleanlab) -> None: + def test_method_db(self, client: Codex) -> None: health = client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_db(self, client: Cleanlab) -> None: + def test_raw_response_db(self, client: Codex) -> None: response = client.health.with_raw_response.db() assert response.is_closed is True @@ -57,7 +57,7 @@ def test_raw_response_db(self, client: Cleanlab) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_db(self, client: Cleanlab) -> None: + def test_streaming_response_db(self, client: Codex) -> None: with client.health.with_streaming_response.db() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -68,12 +68,12 @@ def test_streaming_response_db(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_weaviate(self, client: Cleanlab) -> None: + def test_method_weaviate(self, client: Codex) -> None: health = client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_raw_response_weaviate(self, client: Cleanlab) -> None: + def test_raw_response_weaviate(self, client: Codex) -> None: response = client.health.with_raw_response.weaviate() assert response.is_closed is True @@ -82,7 +82,7 @@ def test_raw_response_weaviate(self, client: Cleanlab) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - def test_streaming_response_weaviate(self, client: Cleanlab) -> None: + def test_streaming_response_weaviate(self, client: Codex) -> None: with client.health.with_streaming_response.weaviate() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -97,12 +97,12 @@ class TestAsyncHealth: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_check(self, async_client: AsyncCleanlab) -> None: + async def test_method_check(self, async_client: AsyncCodex) -> None: health = await async_client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_check(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_check(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.check() assert response.is_closed is True @@ -111,7 +111,7 @@ async def test_raw_response_check(self, async_client: AsyncCleanlab) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_check(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.check() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -122,12 +122,12 @@ async def test_streaming_response_check(self, async_client: AsyncCleanlab) -> No assert cast(Any, response.is_closed) is True @parametrize - async def test_method_db(self, async_client: AsyncCleanlab) -> None: + async def test_method_db(self, async_client: AsyncCodex) -> None: health = await async_client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_db(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_db(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.db() assert response.is_closed is True @@ -136,7 +136,7 @@ async def test_raw_response_db(self, async_client: AsyncCleanlab) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_db(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.db() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -147,12 +147,12 @@ async def test_streaming_response_db(self, async_client: AsyncCleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - async def test_method_weaviate(self, async_client: AsyncCleanlab) -> None: + async def test_method_weaviate(self, async_client: AsyncCodex) -> None: health = await async_client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_raw_response_weaviate(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.weaviate() assert response.is_closed is True @@ -161,7 +161,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCleanlab) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) @parametrize - async def test_streaming_response_weaviate(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.weaviate() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index fa17b24b..2d627c70 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from codex.types import OrganizationSchemaPublic from tests.utils import assert_matches_type @@ -18,14 +18,14 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: Cleanlab) -> None: + def test_method_retrieve(self, client: Codex) -> None: organization = client.organizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Cleanlab) -> None: + def test_raw_response_retrieve(self, client: Codex) -> None: response = client.organizations.with_raw_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -36,7 +36,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None: assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Cleanlab) -> None: + def test_streaming_response_retrieve(self, client: Codex) -> None: with client.organizations.with_streaming_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -49,7 +49,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_retrieve(self, client: Cleanlab) -> None: + def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): client.organizations.with_raw_response.retrieve( "", @@ -60,14 +60,14 @@ class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: organization = await async_client.organizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.with_raw_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -78,7 +78,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.organizations.with_streaming_response.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -91,7 +91,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): await async_client.organizations.with_raw_response.retrieve( "", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index c516e727..7e79d8e8 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from codex.types import ( ProjectListResponse, ProjectReturnSchema, @@ -21,7 +21,7 @@ class TestProjects: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_create(self, client: Cleanlab) -> None: + def test_method_create(self, client: Codex) -> None: project = client.projects.create( config={}, name="name", @@ -30,7 +30,7 @@ def test_method_create(self, client: Cleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_method_create_with_all_params(self, client: Cleanlab) -> None: + def test_method_create_with_all_params(self, client: Codex) -> None: project = client.projects.create( config={"max_distance": 0}, name="name", @@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Cleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_create(self, client: Cleanlab) -> None: + def test_raw_response_create(self, client: Codex) -> None: response = client.projects.with_raw_response.create( config={}, name="name", @@ -53,7 +53,7 @@ def test_raw_response_create(self, client: Cleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_create(self, client: Cleanlab) -> None: + def test_streaming_response_create(self, client: Codex) -> None: with client.projects.with_streaming_response.create( config={}, name="name", @@ -68,14 +68,14 @@ def test_streaming_response_create(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_retrieve(self, client: Cleanlab) -> None: + def test_method_retrieve(self, client: Codex) -> None: project = client.projects.retrieve( 0, ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Cleanlab) -> None: + def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.with_raw_response.retrieve( 0, ) @@ -86,7 +86,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Cleanlab) -> None: + def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.with_streaming_response.retrieve( 0, ) as response: @@ -99,7 +99,7 @@ def test_streaming_response_retrieve(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_update(self, client: Cleanlab) -> None: + def test_method_update(self, client: Codex) -> None: project = client.projects.update( project_id=0, config={}, @@ -108,7 +108,7 @@ def test_method_update(self, client: Cleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: Cleanlab) -> None: + def test_method_update_with_all_params(self, client: Codex) -> None: project = client.projects.update( project_id=0, config={"max_distance": 0}, @@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: Cleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_raw_response_update(self, client: Cleanlab) -> None: + def test_raw_response_update(self, client: Codex) -> None: response = client.projects.with_raw_response.update( project_id=0, config={}, @@ -131,7 +131,7 @@ def test_raw_response_update(self, client: Cleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - def test_streaming_response_update(self, client: Cleanlab) -> None: + def test_streaming_response_update(self, client: Codex) -> None: with client.projects.with_streaming_response.update( project_id=0, config={}, @@ -146,14 +146,14 @@ def test_streaming_response_update(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_list(self, client: Cleanlab) -> None: + def test_method_list(self, client: Codex) -> None: project = client.projects.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - def test_raw_response_list(self, client: Cleanlab) -> None: + def test_raw_response_list(self, client: Codex) -> None: response = client.projects.with_raw_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -164,7 +164,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None: assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Cleanlab) -> None: + def test_streaming_response_list(self, client: Codex) -> None: with client.projects.with_streaming_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -177,14 +177,14 @@ def test_streaming_response_list(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_delete(self, client: Cleanlab) -> None: + def test_method_delete(self, client: Codex) -> None: project = client.projects.delete( 0, ) assert project is None @parametrize - def test_raw_response_delete(self, client: Cleanlab) -> None: + def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.with_raw_response.delete( 0, ) @@ -195,7 +195,7 @@ def test_raw_response_delete(self, client: Cleanlab) -> None: assert project is None @parametrize - def test_streaming_response_delete(self, client: Cleanlab) -> None: + def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.with_streaming_response.delete( 0, ) as response: @@ -208,14 +208,14 @@ def test_streaming_response_delete(self, client: Cleanlab) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_export(self, client: Cleanlab) -> None: + def test_method_export(self, client: Codex) -> None: project = client.projects.export( 0, ) assert_matches_type(object, project, path=["response"]) @parametrize - def test_raw_response_export(self, client: Cleanlab) -> None: + def test_raw_response_export(self, client: Codex) -> None: response = client.projects.with_raw_response.export( 0, ) @@ -226,7 +226,7 @@ def test_raw_response_export(self, client: Cleanlab) -> None: assert_matches_type(object, project, path=["response"]) @parametrize - def test_streaming_response_export(self, client: Cleanlab) -> None: + def test_streaming_response_export(self, client: Codex) -> None: with client.projects.with_streaming_response.export( 0, ) as response: @@ -243,7 +243,7 @@ class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, async_client: AsyncCleanlab) -> None: + async def test_method_create(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( config={}, name="name", @@ -252,7 +252,7 @@ async def test_method_create(self, async_client: AsyncCleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) -> None: + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( config={"max_distance": 0}, name="name", @@ -262,7 +262,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCleanlab) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.create( config={}, name="name", @@ -275,7 +275,7 @@ async def test_raw_response_create(self, async_client: AsyncCleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.create( config={}, name="name", @@ -290,14 +290,14 @@ async def test_streaming_response_create(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: project = await async_client.projects.retrieve( 0, ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.retrieve( 0, ) @@ -308,7 +308,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.retrieve( 0, ) as response: @@ -321,7 +321,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> assert cast(Any, response.is_closed) is True @parametrize - async def test_method_update(self, async_client: AsyncCleanlab) -> None: + async def test_method_update(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( project_id=0, config={}, @@ -330,7 +330,7 @@ async def test_method_update(self, async_client: AsyncCleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( project_id=0, config={"max_distance": 0}, @@ -340,7 +340,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCleanlab) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.update( project_id=0, config={}, @@ -353,7 +353,7 @@ async def test_raw_response_update(self, async_client: AsyncCleanlab) -> None: assert_matches_type(ProjectReturnSchema, project, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.update( project_id=0, config={}, @@ -368,14 +368,14 @@ async def test_streaming_response_update(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_method_list(self, async_client: AsyncCleanlab) -> None: + async def test_method_list(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -386,7 +386,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: assert_matches_type(ProjectListResponse, project, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.list( organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: @@ -399,14 +399,14 @@ async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> Non assert cast(Any, response.is_closed) is True @parametrize - async def test_method_delete(self, async_client: AsyncCleanlab) -> None: + async def test_method_delete(self, async_client: AsyncCodex) -> None: project = await async_client.projects.delete( 0, ) assert project is None @parametrize - async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.delete( 0, ) @@ -417,7 +417,7 @@ async def test_raw_response_delete(self, async_client: AsyncCleanlab) -> None: assert project is None @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.delete( 0, ) as response: @@ -430,14 +430,14 @@ async def test_streaming_response_delete(self, async_client: AsyncCleanlab) -> N assert cast(Any, response.is_closed) is True @parametrize - async def test_method_export(self, async_client: AsyncCleanlab) -> None: + async def test_method_export(self, async_client: AsyncCodex) -> None: project = await async_client.projects.export( 0, ) assert_matches_type(object, project, path=["response"]) @parametrize - async def test_raw_response_export(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_export(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.export( 0, ) @@ -448,7 +448,7 @@ async def test_raw_response_export(self, async_client: AsyncCleanlab) -> None: assert_matches_type(object, project, path=["response"]) @parametrize - async def test_streaming_response_export(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_export(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.export( 0, ) as response: diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index 36a68194..55bba35f 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex.types.users import UserSchema @@ -18,12 +18,12 @@ class TestAPIKey: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_refresh(self, client: Cleanlab) -> None: + def test_method_refresh(self, client: Codex) -> None: api_key = client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - def test_raw_response_refresh(self, client: Cleanlab) -> None: + def test_raw_response_refresh(self, client: Codex) -> None: response = client.users.myself.api_key.with_raw_response.refresh() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_refresh(self, client: Cleanlab) -> None: assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - def test_streaming_response_refresh(self, client: Cleanlab) -> None: + def test_streaming_response_refresh(self, client: Codex) -> None: with client.users.myself.api_key.with_streaming_response.refresh() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncAPIKey: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_refresh(self, async_client: AsyncCleanlab) -> None: + async def test_method_refresh(self, async_client: AsyncCodex) -> None: api_key = await async_client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - async def test_raw_response_refresh(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.api_key.with_raw_response.refresh() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCleanlab) -> None: assert_matches_type(UserSchema, api_key, path=["response"]) @parametrize - async def test_streaming_response_refresh(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.api_key.with_streaming_response.refresh() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py index d50c4742..da485882 100644 --- a/tests/api_resources/users/myself/test_organizations.py +++ b/tests/api_resources/users/myself/test_organizations.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex.types.users.myself import UserOrganizationsSchema @@ -18,12 +18,12 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_list(self, client: Cleanlab) -> None: + def test_method_list(self, client: Codex) -> None: organization = client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - def test_raw_response_list(self, client: Cleanlab) -> None: + def test_raw_response_list(self, client: Codex) -> None: response = client.users.myself.organizations.with_raw_response.list() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_list(self, client: Cleanlab) -> None: assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Cleanlab) -> None: + def test_streaming_response_list(self, client: Codex) -> None: with client.users.myself.organizations.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_list(self, async_client: AsyncCleanlab) -> None: + async def test_method_list(self, async_client: AsyncCodex) -> None: organization = await async_client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.organizations.with_raw_response.list() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_list(self, async_client: AsyncCleanlab) -> None: assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) @parametrize - async def test_streaming_response_list(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.organizations.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index f90be5c0..a206d1f9 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -7,7 +7,7 @@ import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex.types.users import UserSchemaPublic @@ -18,12 +18,12 @@ class TestMyself: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: Cleanlab) -> None: + def test_method_retrieve(self, client: Codex) -> None: myself = client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: Cleanlab) -> None: + def test_raw_response_retrieve(self, client: Codex) -> None: response = client.users.myself.with_raw_response.retrieve() assert response.is_closed is True @@ -32,7 +32,7 @@ def test_raw_response_retrieve(self, client: Cleanlab) -> None: assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: Cleanlab) -> None: + def test_streaming_response_retrieve(self, client: Codex) -> None: with client.users.myself.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,12 +47,12 @@ class TestAsyncMyself: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: myself = await async_client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.with_raw_response.retrieve() assert response.is_closed is True @@ -61,7 +61,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCleanlab) -> None: assert_matches_type(UserSchemaPublic, myself, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCleanlab) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/conftest.py b/tests/conftest.py index 40cbc004..8823ef7a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import pytest from pytest_asyncio import is_async_test -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest @@ -30,20 +30,20 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: @pytest.fixture(scope="session") -def client(request: FixtureRequest) -> Iterator[Cleanlab]: +def client(request: FixtureRequest) -> Iterator[Codex]: strict = getattr(request, "param", True) if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Cleanlab(base_url=base_url, _strict_response_validation=strict) as client: + with Codex(base_url=base_url, _strict_response_validation=strict) as client: yield client @pytest.fixture(scope="session") -async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCleanlab]: +async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]: strict = getattr(request, "param", True) if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - async with AsyncCleanlab(base_url=base_url, _strict_response_validation=strict) as client: + async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index b9a2b4b9..4e76d238 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -20,7 +20,7 @@ from respx import MockRouter from pydantic import ValidationError -from codex import Cleanlab, AsyncCleanlab, APIResponseValidationError +from codex import Codex, AsyncCodex, APIResponseValidationError from codex._types import Omit from codex._models import BaseModel, FinalRequestOptions from codex._constants import RAW_RESPONSE_HEADER @@ -42,7 +42,7 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 -def _get_open_connections(client: Cleanlab | AsyncCleanlab) -> int: +def _get_open_connections(client: Codex | AsyncCodex) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -50,8 +50,8 @@ def _get_open_connections(client: Cleanlab | AsyncCleanlab) -> int: return len(pool._requests) -class TestCleanlab: - client = Cleanlab(base_url=base_url, _strict_response_validation=True) +class TestCodex: + client = Codex(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -94,7 +94,7 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -126,7 +126,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -249,7 +249,7 @@ def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -258,7 +258,7 @@ def test_client_timeout_option(self) -> None: 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: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -266,7 +266,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -274,7 +274,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -283,15 +283,15 @@ def test_http_client_timeout_option(self) -> None: async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: - Cleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) + Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) def test_default_headers_option(self) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = Cleanlab( + client2 = Codex( base_url=base_url, _strict_response_validation=True, default_headers={ @@ -304,7 +304,7 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) + client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -416,7 +416,7 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, client: Cleanlab) -> None: + def test_multipart_repeating_array(self, client: Codex) -> None: request = client._build_request( FinalRequestOptions.construct( method="get", @@ -503,7 +503,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Cleanlab(base_url="https://example.com/from_init", _strict_response_validation=True) + client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -511,23 +511,23 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" def test_base_url_env(self) -> None: - with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): - client = Cleanlab(_strict_response_validation=True) + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + client = Codex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness - with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - Cleanlab(_strict_response_validation=True, environment="production") + Codex(_strict_response_validation=True, environment="production") - client = Cleanlab(base_url=None, _strict_response_validation=True, environment="production") + client = Codex(base_url=None, _strict_response_validation=True, environment="production") assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", [ - Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - Cleanlab( + Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Codex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -535,7 +535,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: Cleanlab) -> None: + def test_base_url_trailing_slash(self, client: Codex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -548,8 +548,8 @@ def test_base_url_trailing_slash(self, client: Cleanlab) -> None: @pytest.mark.parametrize( "client", [ - Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - Cleanlab( + Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Codex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -557,7 +557,7 @@ def test_base_url_trailing_slash(self, client: Cleanlab) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None: + def test_base_url_no_trailing_slash(self, client: Codex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -570,8 +570,8 @@ def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None: @pytest.mark.parametrize( "client", [ - Cleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - Cleanlab( + Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Codex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.Client(), @@ -579,7 +579,7 @@ def test_base_url_no_trailing_slash(self, client: Cleanlab) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: Cleanlab) -> None: + def test_absolute_request_url(self, client: Codex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -590,7 +590,7 @@ def test_absolute_request_url(self, client: Cleanlab) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True) + client = Codex(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -601,7 +601,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True) + client = Codex(base_url=base_url, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -622,7 +622,7 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Cleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -631,12 +631,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Cleanlab(base_url=base_url, _strict_response_validation=True) + strict_client = Codex(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Cleanlab(base_url=base_url, _strict_response_validation=False) + client = Codex(base_url=base_url, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -664,7 +664,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Cleanlab(base_url=base_url, _strict_response_validation=True) + client = Codex(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -707,7 +707,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non @pytest.mark.parametrize("failure_mode", ["status", "exception"]) def test_retries_taken( self, - client: Cleanlab, + client: Codex, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -737,9 +737,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_omit_retry_count_header( - self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter - ) -> None: + def test_omit_retry_count_header(self, client: Codex, failures_before_success: int, respx_mock: MockRouter) -> None: client = client.with_options(max_retries=4) nb_retries = 0 @@ -766,7 +764,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_overwrite_retry_count_header( - self, client: Cleanlab, failures_before_success: int, respx_mock: MockRouter + self, client: Codex, failures_before_success: int, respx_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) @@ -791,8 +789,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" -class TestAsyncCleanlab: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) +class TestAsyncCodex: + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -837,7 +835,7 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -869,7 +867,7 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -992,7 +990,7 @@ async def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) async def test_client_timeout_option(self) -> None: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1001,7 +999,7 @@ async def test_client_timeout_option(self) -> None: 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: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1009,7 +1007,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1017,7 +1015,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1026,15 +1024,15 @@ async def test_http_client_timeout_option(self) -> None: def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: - AsyncCleanlab(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) + AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) def test_default_headers_option(self) -> None: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncCleanlab( + client2 = AsyncCodex( base_url=base_url, _strict_response_validation=True, default_headers={ @@ -1047,9 +1045,7 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = AsyncCleanlab( - base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"} - ) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -1161,7 +1157,7 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, async_client: AsyncCleanlab) -> None: + def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None: request = async_client._build_request( FinalRequestOptions.construct( method="get", @@ -1248,7 +1244,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = AsyncCleanlab(base_url="https://example.com/from_init", _strict_response_validation=True) + client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -1256,23 +1252,23 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" def test_base_url_env(self) -> None: - with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): - client = AsyncCleanlab(_strict_response_validation=True) + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): + client = AsyncCodex(_strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness - with update_env(CLEANLAB_BASE_URL="http://localhost:5000/from/env"): + with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - AsyncCleanlab(_strict_response_validation=True, environment="production") + AsyncCodex(_strict_response_validation=True, environment="production") - client = AsyncCleanlab(base_url=None, _strict_response_validation=True, environment="production") + client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production") assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", [ - AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCleanlab( + AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1280,7 +1276,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None: + def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1293,8 +1289,8 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None: @pytest.mark.parametrize( "client", [ - AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCleanlab( + AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1302,7 +1298,7 @@ def test_base_url_trailing_slash(self, client: AsyncCleanlab) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None: + def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1315,8 +1311,8 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None: @pytest.mark.parametrize( "client", [ - AsyncCleanlab(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), - AsyncCleanlab( + AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCodex( base_url="http://localhost:5000/custom/path/", _strict_response_validation=True, http_client=httpx.AsyncClient(), @@ -1324,7 +1320,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCleanlab) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncCleanlab) -> None: + def test_absolute_request_url(self, client: AsyncCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1335,7 +1331,7 @@ def test_absolute_request_url(self, client: AsyncCleanlab) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1347,7 +1343,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1369,7 +1365,7 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - AsyncCleanlab(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -1379,12 +1375,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) + strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=False) + client = AsyncCodex(base_url=base_url, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1413,7 +1409,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncCleanlab(base_url=base_url, _strict_response_validation=True) + client = AsyncCodex(base_url=base_url, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -1457,7 +1453,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, - async_client: AsyncCleanlab, + async_client: AsyncCodex, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -1489,7 +1485,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio async def test_omit_retry_count_header( - self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) @@ -1518,7 +1514,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio async def test_overwrite_retry_count_header( - self, async_client: AsyncCleanlab, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) diff --git a/tests/test_response.py b/tests/test_response.py index 55b100ce..224eb22e 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -6,7 +6,7 @@ import pytest import pydantic -from codex import Cleanlab, BaseModel, AsyncCleanlab +from codex import Codex, BaseModel, AsyncCodex from codex._response import ( APIResponse, BaseAPIResponse, @@ -56,7 +56,7 @@ def test_extract_response_type_binary_response() -> None: class PydanticModel(pydantic.BaseModel): ... -def test_response_parse_mismatched_basemodel(client: Cleanlab) -> None: +def test_response_parse_mismatched_basemodel(client: Codex) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -74,7 +74,7 @@ def test_response_parse_mismatched_basemodel(client: Cleanlab) -> None: @pytest.mark.asyncio -async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCleanlab) -> None: +async def test_async_response_parse_mismatched_basemodel(async_client: AsyncCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -91,7 +91,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncClea await response.parse(to=PydanticModel) -def test_response_parse_custom_stream(client: Cleanlab) -> None: +def test_response_parse_custom_stream(client: Codex) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -106,7 +106,7 @@ def test_response_parse_custom_stream(client: Cleanlab) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_stream(async_client: AsyncCleanlab) -> None: +async def test_async_response_parse_custom_stream(async_client: AsyncCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -125,7 +125,7 @@ class CustomModel(BaseModel): bar: int -def test_response_parse_custom_model(client: Cleanlab) -> None: +def test_response_parse_custom_model(client: Codex) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -141,7 +141,7 @@ def test_response_parse_custom_model(client: Cleanlab) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_model(async_client: AsyncCleanlab) -> None: +async def test_async_response_parse_custom_model(async_client: AsyncCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -156,7 +156,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncCleanlab) -> assert obj.bar == 2 -def test_response_parse_annotated_type(client: Cleanlab) -> None: +def test_response_parse_annotated_type(client: Codex) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -173,7 +173,7 @@ def test_response_parse_annotated_type(client: Cleanlab) -> None: assert obj.bar == 2 -async def test_async_response_parse_annotated_type(async_client: AsyncCleanlab) -> None: +async def test_async_response_parse_annotated_type(async_client: AsyncCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -201,7 +201,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncCleanlab) ("FalSe", False), ], ) -def test_response_parse_bool(client: Cleanlab, content: str, expected: bool) -> None: +def test_response_parse_bool(client: Codex, content: str, expected: bool) -> None: response = APIResponse( raw=httpx.Response(200, content=content), client=client, @@ -226,7 +226,7 @@ def test_response_parse_bool(client: Cleanlab, content: str, expected: bool) -> ("FalSe", False), ], ) -async def test_async_response_parse_bool(client: AsyncCleanlab, content: str, expected: bool) -> None: +async def test_async_response_parse_bool(client: AsyncCodex, content: str, expected: bool) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=content), client=client, @@ -245,7 +245,7 @@ class OtherModel(BaseModel): @pytest.mark.parametrize("client", [False], indirect=True) # loose validation -def test_response_parse_expect_model_union_non_json_content(client: Cleanlab) -> None: +def test_response_parse_expect_model_union_non_json_content(client: Codex) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=client, @@ -262,7 +262,7 @@ def test_response_parse_expect_model_union_non_json_content(client: Cleanlab) -> @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: AsyncCleanlab) -> None: +async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncCodex) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=async_client, diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 9d0b6456..443b5aa5 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -5,13 +5,13 @@ import httpx import pytest -from codex import Cleanlab, AsyncCleanlab +from codex import Codex, AsyncCodex from codex._streaming import Stream, AsyncStream, ServerSentEvent @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_basic(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_basic(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b"event: completion\n" yield b'data: {"foo":true}\n' @@ -28,7 +28,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_missing_event(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_data_missing_event(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b'data: {"foo":true}\n' yield b"\n" @@ -44,7 +44,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_event_missing_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_event_missing_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -60,7 +60,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_multiple_events(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -82,7 +82,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events_with_data(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_multiple_events_with_data(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo":true}\n' @@ -106,7 +106,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines_with_empty_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_multiple_data_lines_with_empty_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -128,7 +128,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_json_escaped_double_new_line(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_data_json_escaped_double_new_line(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo": "my long\\n\\ncontent"}' @@ -145,7 +145,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines(sync: bool, client: Cleanlab, async_client: AsyncCleanlab) -> None: +async def test_multiple_data_lines(sync: bool, client: Codex, async_client: AsyncCodex) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -165,8 +165,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_special_new_line_character( sync: bool, - client: Cleanlab, - async_client: AsyncCleanlab, + client: Codex, + async_client: AsyncCodex, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":" culpa"}\n' @@ -196,8 +196,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_multi_byte_character_multiple_chunks( sync: bool, - client: Cleanlab, - async_client: AsyncCleanlab, + client: Codex, + async_client: AsyncCodex, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":"' @@ -237,8 +237,8 @@ def make_event_iterator( content: Iterator[bytes], *, sync: bool, - client: Cleanlab, - async_client: AsyncCleanlab, + client: Codex, + async_client: AsyncCodex, ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: if sync: return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() From 1c7da06305804f627b15e06dee377ab22a41601d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:14:43 +0000 Subject: [PATCH 007/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 71d28223..b9a5b6ba 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 28 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-f4a89fd69be5055c12ea6d3aa795af4161605848eb9e010e1a7be22f4b1b712c.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-5691cf2ac1617d7acc6532acfb2c08e151b48e6b045cdaf1715ff3843f611eb0.yml From fa64ac38902c6d243e45166922181b00b5e79880 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:25:17 +0000 Subject: [PATCH 008/320] feat(api): manual updates --- .stats.yml | 2 +- api.md | 7 ++- src/codex/resources/projects/access_keys.py | 51 +++++++++++++++++++ src/codex/types/projects/__init__.py | 3 ++ ...access_key_retrieve_project_id_response.py | 7 +++ .../projects/test_access_keys.py | 51 +++++++++++++++++++ 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 src/codex/types/projects/access_key_retrieve_project_id_response.py diff --git a/.stats.yml b/.stats.yml index b9a5b6ba..a156c044 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 28 +configured_endpoints: 29 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-5691cf2ac1617d7acc6532acfb2c08e151b48e6b045cdaf1715ff3843f611eb0.yml diff --git a/api.md b/api.md index c9ef6c6c..672aeeb6 100644 --- a/api.md +++ b/api.md @@ -94,7 +94,11 @@ Methods: Types: ```python -from codex.types.projects import AccessKeySchema, AccessKeyListResponse +from codex.types.projects import ( + AccessKeySchema, + AccessKeyListResponse, + AccessKeyRetrieveProjectIDResponse, +) ``` Methods: @@ -104,6 +108,7 @@ Methods: - client.projects.access_keys.update(access_key_id, \*, project_id, \*\*params) -> AccessKeySchema - client.projects.access_keys.list(project_id) -> AccessKeyListResponse - client.projects.access_keys.delete(access_key_id, \*, project_id) -> None +- client.projects.access_keys.retrieve_project_id() -> AccessKeyRetrieveProjectIDResponse - client.projects.access_keys.revoke(access_key_id, \*, project_id) -> None ## Knowledge diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 7fc21ae9..2882e65b 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -24,6 +24,7 @@ from ...types.projects import access_key_create_params, access_key_update_params from ...types.projects.access_key_schema import AccessKeySchema from ...types.projects.access_key_list_response import AccessKeyListResponse +from ...types.projects.access_key_retrieve_project_id_response import AccessKeyRetrieveProjectIDResponse __all__ = ["AccessKeysResource", "AsyncAccessKeysResource"] @@ -229,6 +230,25 @@ def delete( cast_to=NoneType, ) + def retrieve_project_id( + self, + *, + # 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, + ) -> AccessKeyRetrieveProjectIDResponse: + """Get the project ID from an access key.""" + return self._get( + "/api/projects/id_from_access_key", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=int, + ) + def revoke( self, access_key_id: int, @@ -464,6 +484,25 @@ async def delete( cast_to=NoneType, ) + async def retrieve_project_id( + self, + *, + # 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, + ) -> AccessKeyRetrieveProjectIDResponse: + """Get the project ID from an access key.""" + return await self._get( + "/api/projects/id_from_access_key", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=int, + ) + async def revoke( self, access_key_id: int, @@ -517,6 +556,9 @@ def __init__(self, access_keys: AccessKeysResource) -> None: self.delete = to_raw_response_wrapper( access_keys.delete, ) + self.retrieve_project_id = to_raw_response_wrapper( + access_keys.retrieve_project_id, + ) self.revoke = to_raw_response_wrapper( access_keys.revoke, ) @@ -541,6 +583,9 @@ def __init__(self, access_keys: AsyncAccessKeysResource) -> None: self.delete = async_to_raw_response_wrapper( access_keys.delete, ) + self.retrieve_project_id = async_to_raw_response_wrapper( + access_keys.retrieve_project_id, + ) self.revoke = async_to_raw_response_wrapper( access_keys.revoke, ) @@ -565,6 +610,9 @@ def __init__(self, access_keys: AccessKeysResource) -> None: self.delete = to_streamed_response_wrapper( access_keys.delete, ) + self.retrieve_project_id = to_streamed_response_wrapper( + access_keys.retrieve_project_id, + ) self.revoke = to_streamed_response_wrapper( access_keys.revoke, ) @@ -589,6 +637,9 @@ def __init__(self, access_keys: AsyncAccessKeysResource) -> None: self.delete = async_to_streamed_response_wrapper( access_keys.delete, ) + self.retrieve_project_id = async_to_streamed_response_wrapper( + access_keys.retrieve_project_id, + ) self.revoke = async_to_streamed_response_wrapper( access_keys.revoke, ) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 89e09d61..9813f780 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -13,3 +13,6 @@ from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams from .knowledge_add_question_params import KnowledgeAddQuestionParams as KnowledgeAddQuestionParams +from .access_key_retrieve_project_id_response import ( + AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse, +) diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py new file mode 100644 index 00000000..5659cd21 --- /dev/null +++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["AccessKeyRetrieveProjectIDResponse"] + +AccessKeyRetrieveProjectIDResponse: TypeAlias = int diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index 4fa1636f..eab961cc 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -13,6 +13,7 @@ from codex.types.projects import ( AccessKeySchema, AccessKeyListResponse, + AccessKeyRetrieveProjectIDResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -212,6 +213,31 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_retrieve_project_id(self, client: Codex) -> None: + access_key = client.projects.access_keys.retrieve_project_id() + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + + @parametrize + def test_raw_response_retrieve_project_id(self, client: Codex) -> None: + response = client.projects.access_keys.with_raw_response.retrieve_project_id() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = response.parse() + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: + with client.projects.access_keys.with_streaming_response.retrieve_project_id() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = response.parse() + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_revoke(self, client: Codex) -> None: access_key = client.projects.access_keys.revoke( @@ -441,6 +467,31 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None: + access_key = await async_client.projects.access_keys.retrieve_project_id() + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.access_keys.with_raw_response.retrieve_project_id() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + access_key = await response.parse() + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_project_id(self, async_client: AsyncCodex) -> None: + async with async_client.projects.access_keys.with_streaming_response.retrieve_project_id() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_key = await response.parse() + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_revoke(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.revoke( From f9167e0a25690d70f896f59945efd64ca33dd896 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:12:27 +0000 Subject: [PATCH 009/320] chore: go live (#1) --- .github/workflows/publish-pypi.yml | 31 +++++++++ .github/workflows/release-doctor.yml | 21 ++++++ .release-please-manifest.json | 3 + CONTRIBUTING.md | 4 +- README.md | 10 +-- bin/check-release-environment | 21 ++++++ pyproject.toml | 6 +- release-please-config.json | 66 +++++++++++++++++++ src/codex/_version.py | 2 +- src/codex/resources/health.py | 8 +-- src/codex/resources/organizations/billing.py | 8 +-- .../resources/organizations/organizations.py | 8 +-- src/codex/resources/projects/access_keys.py | 8 +-- src/codex/resources/projects/knowledge.py | 8 +-- src/codex/resources/projects/projects.py | 8 +-- src/codex/resources/users/myself/api_key.py | 8 +-- src/codex/resources/users/myself/myself.py | 8 +-- .../resources/users/myself/organizations.py | 8 +-- src/codex/resources/users/users.py | 8 +-- 19 files changed, 193 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/publish-pypi.yml create mode 100644 .github/workflows/release-doctor.yml create mode 100644 .release-please-manifest.json create mode 100644 bin/check-release-environment create mode 100644 release-please-config.json diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 00000000..5ed611c7 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,31 @@ +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to PyPI in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/cleanlab/codex-python/actions/workflows/publish-pypi.yml +name: Publish PyPI +on: + workflow_dispatch: + + release: + types: [published] + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - 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_INSTALL_OPTION: '--yes' + + - name: Publish to PyPI + run: | + bash ./bin/publish-pypi + env: + PYPI_TOKEN: ${{ secrets.CODEX_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 00000000..2ce3cdc2 --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,21 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'cleanlab/codex-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v4 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: + PYPI_TOKEN: ${{ secrets.CODEX_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..c4762802 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.1-alpha.0" +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7111bafc..b9fa9a13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ pip install git+ssh://git@github.com/stainless-sdks/codex-python.git +$ pip install git+ssh://git@github.com/cleanlab/codex-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -121,7 +121,7 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/stainless-sdks/codex-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/cleanlab/codex-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. ### Publish manually diff --git a/README.md b/README.md index f84291e1..683c158c 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean ## Installation ```sh -# install from this staging repo -pip install git+ssh://git@github.com/stainless-sdks/codex-python.git +# install from the production repo +pip install git+ssh://git@github.com/cleanlab/codex-python.git ``` > [!NOTE] @@ -230,9 +230,9 @@ project = response.parse() # get the object that `projects.create()` would have print(project.id) ``` -These methods return an [`APIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) object. +These methods return an [`APIResponse`](https://github.com/cleanlab/codex-python/tree/main/src/codex/_response.py) object. -The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. +The async client returns an [`AsyncAPIResponse`](https://github.com/cleanlab/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. #### `.with_streaming_response` @@ -340,7 +340,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/codex-python/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/cleanlab/codex-python/issues) with questions, bugs, or suggestions. ### Determining the installed version diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 00000000..a1446a75 --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +errors=() + +if [ -z "${PYPI_TOKEN}" ]; then + errors+=("The CODEX_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") +fi + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" diff --git a/pyproject.toml b/pyproject.toml index 3fba9e70..248f7eaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,8 +34,8 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/stainless-sdks/codex-python" -Repository = "https://github.com/stainless-sdks/codex-python" +Homepage = "https://github.com/cleanlab/codex-python" +Repository = "https://github.com/cleanlab/codex-python" @@ -122,7 +122,7 @@ path = "README.md" [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] # replace relative links with absolute links pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' -replacement = '[\1](https://github.com/stainless-sdks/codex-python/tree/main/\g<2>)' +replacement = '[\1](https://github.com/cleanlab/codex-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..8fd34900 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,66 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "release-type": "python", + "extra-files": [ + "src/codex/_version.py" + ] +} \ No newline at end of file diff --git a/src/codex/_version.py b/src/codex/_version.py index 7ee46487..fd0542b8 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.0.1-alpha.0" +__version__ = "0.0.1-alpha.0" # x-release-please-version diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py index 29c374e6..b50884e0 100644 --- a/src/codex/resources/health.py +++ b/src/codex/resources/health.py @@ -26,7 +26,7 @@ def with_raw_response(self) -> HealthResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return HealthResourceWithRawResponse(self) @@ -35,7 +35,7 @@ def with_streaming_response(self) -> HealthResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return HealthResourceWithStreamingResponse(self) @@ -104,7 +104,7 @@ def with_raw_response(self) -> AsyncHealthResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncHealthResourceWithRawResponse(self) @@ -113,7 +113,7 @@ def with_streaming_response(self) -> AsyncHealthResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncHealthResourceWithStreamingResponse(self) diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing.py index fb234032..9c916d83 100644 --- a/src/codex/resources/organizations/billing.py +++ b/src/codex/resources/organizations/billing.py @@ -27,7 +27,7 @@ def with_raw_response(self) -> BillingResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return BillingResourceWithRawResponse(self) @@ -36,7 +36,7 @@ def with_streaming_response(self) -> BillingResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return BillingResourceWithStreamingResponse(self) @@ -114,7 +114,7 @@ def with_raw_response(self) -> AsyncBillingResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncBillingResourceWithRawResponse(self) @@ -123,7 +123,7 @@ def with_streaming_response(self) -> AsyncBillingResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncBillingResourceWithStreamingResponse(self) diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py index 3aad77e0..e91099e3 100644 --- a/src/codex/resources/organizations/organizations.py +++ b/src/codex/resources/organizations/organizations.py @@ -38,7 +38,7 @@ def with_raw_response(self) -> OrganizationsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return OrganizationsResourceWithRawResponse(self) @@ -47,7 +47,7 @@ def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return OrganizationsResourceWithStreamingResponse(self) @@ -96,7 +96,7 @@ def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncOrganizationsResourceWithRawResponse(self) @@ -105,7 +105,7 @@ def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResp """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncOrganizationsResourceWithStreamingResponse(self) diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 2882e65b..e282ad4f 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -36,7 +36,7 @@ def with_raw_response(self) -> AccessKeysResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AccessKeysResourceWithRawResponse(self) @@ -45,7 +45,7 @@ def with_streaming_response(self) -> AccessKeysResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AccessKeysResourceWithStreamingResponse(self) @@ -290,7 +290,7 @@ def with_raw_response(self) -> AsyncAccessKeysResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncAccessKeysResourceWithRawResponse(self) @@ -299,7 +299,7 @@ def with_streaming_response(self) -> AsyncAccessKeysResourceWithStreamingRespons """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncAccessKeysResourceWithStreamingResponse(self) diff --git a/src/codex/resources/projects/knowledge.py b/src/codex/resources/projects/knowledge.py index 6005f9cc..9c43c669 100644 --- a/src/codex/resources/projects/knowledge.py +++ b/src/codex/resources/projects/knowledge.py @@ -41,7 +41,7 @@ def with_raw_response(self) -> KnowledgeResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return KnowledgeResourceWithRawResponse(self) @@ -50,7 +50,7 @@ def with_streaming_response(self) -> KnowledgeResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return KnowledgeResourceWithStreamingResponse(self) @@ -339,7 +339,7 @@ def with_raw_response(self) -> AsyncKnowledgeResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncKnowledgeResourceWithRawResponse(self) @@ -348,7 +348,7 @@ def with_streaming_response(self) -> AsyncKnowledgeResourceWithStreamingResponse """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncKnowledgeResourceWithStreamingResponse(self) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 20e95366..845669a8 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -58,7 +58,7 @@ def with_raw_response(self) -> ProjectsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return ProjectsResourceWithRawResponse(self) @@ -67,7 +67,7 @@ def with_streaming_response(self) -> ProjectsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return ProjectsResourceWithStreamingResponse(self) @@ -301,7 +301,7 @@ def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncProjectsResourceWithRawResponse(self) @@ -310,7 +310,7 @@ def with_streaming_response(self) -> AsyncProjectsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncProjectsResourceWithStreamingResponse(self) diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py index f4108375..c2784b81 100644 --- a/src/codex/resources/users/myself/api_key.py +++ b/src/codex/resources/users/myself/api_key.py @@ -26,7 +26,7 @@ def with_raw_response(self) -> APIKeyResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return APIKeyResourceWithRawResponse(self) @@ -35,7 +35,7 @@ def with_streaming_response(self) -> APIKeyResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return APIKeyResourceWithStreamingResponse(self) @@ -66,7 +66,7 @@ def with_raw_response(self) -> AsyncAPIKeyResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncAPIKeyResourceWithRawResponse(self) @@ -75,7 +75,7 @@ def with_streaming_response(self) -> AsyncAPIKeyResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncAPIKeyResourceWithStreamingResponse(self) diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py index 51d0b16b..ba4548e5 100644 --- a/src/codex/resources/users/myself/myself.py +++ b/src/codex/resources/users/myself/myself.py @@ -50,7 +50,7 @@ def with_raw_response(self) -> MyselfResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return MyselfResourceWithRawResponse(self) @@ -59,7 +59,7 @@ def with_streaming_response(self) -> MyselfResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return MyselfResourceWithStreamingResponse(self) @@ -98,7 +98,7 @@ def with_raw_response(self) -> AsyncMyselfResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncMyselfResourceWithRawResponse(self) @@ -107,7 +107,7 @@ def with_streaming_response(self) -> AsyncMyselfResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncMyselfResourceWithStreamingResponse(self) diff --git a/src/codex/resources/users/myself/organizations.py b/src/codex/resources/users/myself/organizations.py index 5c37dd69..12e799aa 100644 --- a/src/codex/resources/users/myself/organizations.py +++ b/src/codex/resources/users/myself/organizations.py @@ -26,7 +26,7 @@ def with_raw_response(self) -> OrganizationsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return OrganizationsResourceWithRawResponse(self) @@ -35,7 +35,7 @@ def with_streaming_response(self) -> OrganizationsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return OrganizationsResourceWithStreamingResponse(self) @@ -66,7 +66,7 @@ def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncOrganizationsResourceWithRawResponse(self) @@ -75,7 +75,7 @@ def with_streaming_response(self) -> AsyncOrganizationsResourceWithStreamingResp """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncOrganizationsResourceWithStreamingResponse(self) diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index 4d229677..ea5019c2 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -27,7 +27,7 @@ def with_raw_response(self) -> UsersResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return UsersResourceWithRawResponse(self) @@ -36,7 +36,7 @@ def with_streaming_response(self) -> UsersResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return UsersResourceWithStreamingResponse(self) @@ -52,7 +52,7 @@ def with_raw_response(self) -> AsyncUsersResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/codex-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers """ return AsyncUsersResourceWithRawResponse(self) @@ -61,7 +61,7 @@ def with_streaming_response(self) -> AsyncUsersResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/codex-python#with_streaming_response + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response """ return AsyncUsersResourceWithStreamingResponse(self) From 7307f43fe1d203a665d914ca79c23f8ebc5a0dd4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:57:46 +0000 Subject: [PATCH 010/320] fix: update org info and package name (#4) --- README.md | 9 +++------ SECURITY.md | 2 +- pyproject.toml | 4 ++-- requirements-dev.lock | 12 ++++++------ requirements.lock | 12 ++++++------ 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 683c158c..634367e0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Codex Python API library -[![PyPI version](https://img.shields.io/pypi/v/codex.svg)](https://pypi.org/project/codex/) +[![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg)](https://pypi.org/project/codex-sdk/) The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, @@ -15,13 +15,10 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean ## Installation ```sh -# install from the production repo -pip install git+ssh://git@github.com/cleanlab/codex-python.git +# install from PyPI +pip install --pre codex-sdk ``` -> [!NOTE] -> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex` - ## Usage The full API of this library can be found in [api.md](api.md). diff --git a/SECURITY.md b/SECURITY.md index fcad344e..54f64467 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -20,7 +20,7 @@ or products provided by Codex please follow the respective company's security re ### Codex Terms and Policies -Please contact support@cleanlab.ai for any questions or concerns regarding security of our services. +Please contact team@cleanlab.ai for any questions or concerns regarding security of our services. --- diff --git a/pyproject.toml b/pyproject.toml index 248f7eaa..647b75c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] -name = "codex" +name = "codex-sdk" version = "0.0.1-alpha.0" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "Apache-2.0" authors = [ -{ name = "Codex", email = "support@cleanlab.ai" }, +{ name = "Codex", email = "team@cleanlab.ai" }, ] dependencies = [ "httpx>=0.23.0, <1", diff --git a/requirements-dev.lock b/requirements-dev.lock index 11bf46e6..47eb5e34 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,7 +12,7 @@ annotated-types==0.6.0 # via pydantic anyio==4.4.0 - # via codex + # via codex-sdk # via httpx argcomplete==3.1.2 # via nox @@ -25,7 +25,7 @@ dirty-equals==0.6.0 distlib==0.3.7 # via virtualenv distro==1.8.0 - # via codex + # via codex-sdk exceptiongroup==1.2.2 # via anyio # via pytest @@ -36,7 +36,7 @@ h11==0.14.0 httpcore==1.0.2 # via httpx httpx==0.28.1 - # via codex + # via codex-sdk # via respx idna==3.4 # via anyio @@ -63,7 +63,7 @@ platformdirs==3.11.0 pluggy==1.5.0 # via pytest pydantic==2.10.3 - # via codex + # via codex-sdk pydantic-core==2.27.1 # via pydantic pygments==2.18.0 @@ -85,14 +85,14 @@ six==1.16.0 # via python-dateutil sniffio==1.3.0 # via anyio - # via codex + # via codex-sdk time-machine==2.9.0 tomli==2.0.2 # via mypy # via pytest typing-extensions==4.12.2 # via anyio - # via codex + # via codex-sdk # via mypy # via pydantic # via pydantic-core diff --git a/requirements.lock b/requirements.lock index fb7b045f..5d7fff82 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,13 +12,13 @@ annotated-types==0.6.0 # via pydantic anyio==4.4.0 - # via codex + # via codex-sdk # via httpx certifi==2023.7.22 # via httpcore # via httpx distro==1.8.0 - # via codex + # via codex-sdk exceptiongroup==1.2.2 # via anyio h11==0.14.0 @@ -26,19 +26,19 @@ h11==0.14.0 httpcore==1.0.2 # via httpx httpx==0.28.1 - # via codex + # via codex-sdk idna==3.4 # via anyio # via httpx pydantic==2.10.3 - # via codex + # via codex-sdk pydantic-core==2.27.1 # via pydantic sniffio==1.3.0 # via anyio - # via codex + # via codex-sdk typing-extensions==4.12.2 # via anyio - # via codex + # via codex-sdk # via pydantic # via pydantic-core From 78b8475a328666c2b85b770f96c061f433e7960a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:02:43 +0000 Subject: [PATCH 011/320] fix: disable tests (#6) --- .../organizations/test_billing.py | 16 ++++++ .../projects/test_access_keys.py | 46 ++++++++++++++++ .../api_resources/projects/test_knowledge.py | 54 +++++++++++++++++++ tests/api_resources/test_health.py | 18 +++++++ tests/api_resources/test_organizations.py | 8 +++ tests/api_resources/test_projects.py | 40 ++++++++++++++ .../users/myself/test_api_key.py | 6 +++ .../users/myself/test_organizations.py | 6 +++ tests/api_resources/users/test_myself.py | 6 +++ 9 files changed, 200 insertions(+) diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py index ccb7dc8a..e3bb1d1d 100644 --- a/tests/api_resources/organizations/test_billing.py +++ b/tests/api_resources/organizations/test_billing.py @@ -17,6 +17,7 @@ class TestBilling: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_invoices(self, client: Codex) -> None: billing = client.organizations.billing.invoices( @@ -24,6 +25,7 @@ def test_method_invoices(self, client: Codex) -> None: ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_invoices(self, client: Codex) -> None: response = client.organizations.billing.with_raw_response.invoices( @@ -35,6 +37,7 @@ def test_raw_response_invoices(self, client: Codex) -> None: billing = response.parse() assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_invoices(self, client: Codex) -> None: with client.organizations.billing.with_streaming_response.invoices( @@ -48,6 +51,7 @@ def test_streaming_response_invoices(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_path_params_invoices(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -55,6 +59,7 @@ def test_path_params_invoices(self, client: Codex) -> None: "", ) + @pytest.mark.skip() @parametrize def test_method_usage(self, client: Codex) -> None: billing = client.organizations.billing.usage( @@ -62,6 +67,7 @@ def test_method_usage(self, client: Codex) -> None: ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_usage(self, client: Codex) -> None: response = client.organizations.billing.with_raw_response.usage( @@ -73,6 +79,7 @@ def test_raw_response_usage(self, client: Codex) -> None: billing = response.parse() assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_usage(self, client: Codex) -> None: with client.organizations.billing.with_streaming_response.usage( @@ -86,6 +93,7 @@ def test_streaming_response_usage(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_path_params_usage(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -97,6 +105,7 @@ def test_path_params_usage(self, client: Codex) -> None: class TestAsyncBilling: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_invoices(self, async_client: AsyncCodex) -> None: billing = await async_client.organizations.billing.invoices( @@ -104,6 +113,7 @@ async def test_method_invoices(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.with_raw_response.invoices( @@ -115,6 +125,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: billing = await response.parse() assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.with_streaming_response.invoices( @@ -128,6 +139,7 @@ async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_path_params_invoices(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -135,6 +147,7 @@ async def test_path_params_invoices(self, async_client: AsyncCodex) -> None: "", ) + @pytest.mark.skip() @parametrize async def test_method_usage(self, async_client: AsyncCodex) -> None: billing = await async_client.organizations.billing.usage( @@ -142,6 +155,7 @@ async def test_method_usage(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.with_raw_response.usage( @@ -153,6 +167,7 @@ async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: billing = await response.parse() assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.with_streaming_response.usage( @@ -166,6 +181,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_path_params_usage(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index eab961cc..cdb893e6 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -22,6 +22,7 @@ class TestAccessKeys: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_create(self, client: Codex) -> None: access_key = client.projects.access_keys.create( @@ -30,6 +31,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.create( @@ -40,6 +42,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.create( @@ -52,6 +55,7 @@ def test_raw_response_create(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.create( @@ -66,6 +70,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_retrieve(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve( @@ -74,6 +79,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.retrieve( @@ -86,6 +92,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.retrieve( @@ -100,6 +107,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_update(self, client: Codex) -> None: access_key = client.projects.access_keys.update( @@ -109,6 +117,7 @@ def test_method_update(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_method_update_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.update( @@ -120,6 +129,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_update(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.update( @@ -133,6 +143,7 @@ def test_raw_response_update(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_update(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.update( @@ -148,6 +159,7 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: access_key = client.projects.access_keys.list( @@ -155,6 +167,7 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.list( @@ -166,6 +179,7 @@ def test_raw_response_list(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.list( @@ -179,6 +193,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_delete(self, client: Codex) -> None: access_key = client.projects.access_keys.delete( @@ -187,6 +202,7 @@ def test_method_delete(self, client: Codex) -> None: ) assert access_key is None + @pytest.mark.skip() @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.delete( @@ -199,6 +215,7 @@ def test_raw_response_delete(self, client: Codex) -> None: access_key = response.parse() assert access_key is None + @pytest.mark.skip() @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.delete( @@ -213,11 +230,13 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_retrieve_project_id(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve_project_id() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_retrieve_project_id(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.retrieve_project_id() @@ -227,6 +246,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.retrieve_project_id() as response: @@ -238,6 +258,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_revoke(self, client: Codex) -> None: access_key = client.projects.access_keys.revoke( @@ -246,6 +267,7 @@ def test_method_revoke(self, client: Codex) -> None: ) assert access_key is None + @pytest.mark.skip() @parametrize def test_raw_response_revoke(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.revoke( @@ -258,6 +280,7 @@ def test_raw_response_revoke(self, client: Codex) -> None: access_key = response.parse() assert access_key is None + @pytest.mark.skip() @parametrize def test_streaming_response_revoke(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.revoke( @@ -276,6 +299,7 @@ def test_streaming_response_revoke(self, client: Codex) -> None: class TestAsyncAccessKeys: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( @@ -284,6 +308,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( @@ -294,6 +319,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.create( @@ -306,6 +332,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.create( @@ -320,6 +347,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve( @@ -328,6 +356,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve( @@ -340,6 +369,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve( @@ -354,6 +384,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_update(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( @@ -363,6 +394,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( @@ -374,6 +406,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.update( @@ -387,6 +420,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.update( @@ -402,6 +436,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.list( @@ -409,6 +444,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.list( @@ -420,6 +456,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.list( @@ -433,6 +470,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.delete( @@ -441,6 +479,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: ) assert access_key is None + @pytest.mark.skip() @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.delete( @@ -453,6 +492,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert access_key is None + @pytest.mark.skip() @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.delete( @@ -467,11 +507,13 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve_project_id() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve_project_id() @@ -481,6 +523,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) access_key = await response.parse() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_retrieve_project_id(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve_project_id() as response: @@ -492,6 +535,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_revoke(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.revoke( @@ -500,6 +544,7 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None: ) assert access_key is None + @pytest.mark.skip() @parametrize async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.revoke( @@ -512,6 +557,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert access_key is None + @pytest.mark.skip() @parametrize async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.revoke( diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py index 9052b62d..ac538743 100644 --- a/tests/api_resources/projects/test_knowledge.py +++ b/tests/api_resources/projects/test_knowledge.py @@ -20,6 +20,7 @@ class TestKnowledge: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_create(self, client: Codex) -> None: knowledge = client.projects.knowledge.create( @@ -28,6 +29,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: knowledge = client.projects.knowledge.create( @@ -37,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.create( @@ -49,6 +52,7 @@ def test_raw_response_create(self, client: Codex) -> None: knowledge = response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.create( @@ -63,6 +67,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_retrieve(self, client: Codex) -> None: knowledge = client.projects.knowledge.retrieve( @@ -71,6 +76,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.retrieve( @@ -83,6 +89,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: knowledge = response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.retrieve( @@ -97,6 +104,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): @@ -105,6 +113,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: project_id=0, ) + @pytest.mark.skip() @parametrize def test_method_update(self, client: Codex) -> None: knowledge = client.projects.knowledge.update( @@ -113,6 +122,7 @@ def test_method_update(self, client: Codex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_method_update_with_all_params(self, client: Codex) -> None: knowledge = client.projects.knowledge.update( @@ -123,6 +133,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_update(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.update( @@ -135,6 +146,7 @@ def test_raw_response_update(self, client: Codex) -> None: knowledge = response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_update(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.update( @@ -149,6 +161,7 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_path_params_update(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): @@ -157,6 +170,7 @@ def test_path_params_update(self, client: Codex) -> None: project_id=0, ) + @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: knowledge = client.projects.knowledge.list( @@ -164,6 +178,7 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: knowledge = client.projects.knowledge.list( @@ -177,6 +192,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.list( @@ -188,6 +204,7 @@ def test_raw_response_list(self, client: Codex) -> None: knowledge = response.parse() assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.list( @@ -201,6 +218,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_delete(self, client: Codex) -> None: knowledge = client.projects.knowledge.delete( @@ -209,6 +227,7 @@ def test_method_delete(self, client: Codex) -> None: ) assert knowledge is None + @pytest.mark.skip() @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.delete( @@ -221,6 +240,7 @@ def test_raw_response_delete(self, client: Codex) -> None: knowledge = response.parse() assert knowledge is None + @pytest.mark.skip() @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.delete( @@ -235,6 +255,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_path_params_delete(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): @@ -243,6 +264,7 @@ def test_path_params_delete(self, client: Codex) -> None: project_id=0, ) + @pytest.mark.skip() @parametrize def test_method_add_question(self, client: Codex) -> None: knowledge = client.projects.knowledge.add_question( @@ -251,6 +273,7 @@ def test_method_add_question(self, client: Codex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_add_question(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.add_question( @@ -263,6 +286,7 @@ def test_raw_response_add_question(self, client: Codex) -> None: knowledge = response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_add_question(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.add_question( @@ -277,6 +301,7 @@ def test_streaming_response_add_question(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_query(self, client: Codex) -> None: knowledge = client.projects.knowledge.query( @@ -285,6 +310,7 @@ def test_method_query(self, client: Codex) -> None: ) assert_matches_type(Optional[Entry], knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_query(self, client: Codex) -> None: response = client.projects.knowledge.with_raw_response.query( @@ -297,6 +323,7 @@ def test_raw_response_query(self, client: Codex) -> None: knowledge = response.parse() assert_matches_type(Optional[Entry], knowledge, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_query(self, client: Codex) -> None: with client.projects.knowledge.with_streaming_response.query( @@ -315,6 +342,7 @@ def test_streaming_response_query(self, client: Codex) -> None: class TestAsyncKnowledge: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.create( @@ -323,6 +351,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.create( @@ -332,6 +361,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.create( @@ -344,6 +374,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: knowledge = await response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.create( @@ -358,6 +389,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.retrieve( @@ -366,6 +398,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.retrieve( @@ -378,6 +411,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: knowledge = await response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.retrieve( @@ -392,6 +426,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): @@ -400,6 +435,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: project_id=0, ) + @pytest.mark.skip() @parametrize async def test_method_update(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.update( @@ -408,6 +444,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.update( @@ -418,6 +455,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.update( @@ -430,6 +468,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: knowledge = await response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.update( @@ -444,6 +483,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_path_params_update(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): @@ -452,6 +492,7 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: project_id=0, ) + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.list( @@ -459,6 +500,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.list( @@ -472,6 +514,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No ) assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.list( @@ -483,6 +526,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: knowledge = await response.parse() assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.list( @@ -496,6 +540,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.delete( @@ -504,6 +549,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: ) assert knowledge is None + @pytest.mark.skip() @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.delete( @@ -516,6 +562,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: knowledge = await response.parse() assert knowledge is None + @pytest.mark.skip() @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.delete( @@ -530,6 +577,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_path_params_delete(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): @@ -538,6 +586,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: project_id=0, ) + @pytest.mark.skip() @parametrize async def test_method_add_question(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.add_question( @@ -546,6 +595,7 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.add_question( @@ -558,6 +608,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None knowledge = await response.parse() assert_matches_type(Entry, knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.add_question( @@ -572,6 +623,7 @@ async def test_streaming_response_add_question(self, async_client: AsyncCodex) - assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_query(self, async_client: AsyncCodex) -> None: knowledge = await async_client.projects.knowledge.query( @@ -580,6 +632,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Optional[Entry], knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_query(self, async_client: AsyncCodex) -> None: response = await async_client.projects.knowledge.with_raw_response.query( @@ -592,6 +645,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None: knowledge = await response.parse() assert_matches_type(Optional[Entry], knowledge, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: async with async_client.projects.knowledge.with_streaming_response.query( diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py index be4729ab..09700120 100644 --- a/tests/api_resources/test_health.py +++ b/tests/api_resources/test_health.py @@ -17,11 +17,13 @@ class TestHealth: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_check(self, client: Codex) -> None: health = client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_check(self, client: Codex) -> None: response = client.health.with_raw_response.check() @@ -31,6 +33,7 @@ def test_raw_response_check(self, client: Codex) -> None: health = response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_check(self, client: Codex) -> None: with client.health.with_streaming_response.check() as response: @@ -42,11 +45,13 @@ def test_streaming_response_check(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_db(self, client: Codex) -> None: health = client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_db(self, client: Codex) -> None: response = client.health.with_raw_response.db() @@ -56,6 +61,7 @@ def test_raw_response_db(self, client: Codex) -> None: health = response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_db(self, client: Codex) -> None: with client.health.with_streaming_response.db() as response: @@ -67,11 +73,13 @@ def test_streaming_response_db(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_weaviate(self, client: Codex) -> None: health = client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_weaviate(self, client: Codex) -> None: response = client.health.with_raw_response.weaviate() @@ -81,6 +89,7 @@ def test_raw_response_weaviate(self, client: Codex) -> None: health = response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_weaviate(self, client: Codex) -> None: with client.health.with_streaming_response.weaviate() as response: @@ -96,11 +105,13 @@ def test_streaming_response_weaviate(self, client: Codex) -> None: class TestAsyncHealth: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_check(self, async_client: AsyncCodex) -> None: health = await async_client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_check(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.check() @@ -110,6 +121,7 @@ async def test_raw_response_check(self, async_client: AsyncCodex) -> None: health = await response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.check() as response: @@ -121,11 +133,13 @@ async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_db(self, async_client: AsyncCodex) -> None: health = await async_client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_db(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.db() @@ -135,6 +149,7 @@ async def test_raw_response_db(self, async_client: AsyncCodex) -> None: health = await response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.db() as response: @@ -146,11 +161,13 @@ async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_weaviate(self, async_client: AsyncCodex) -> None: health = await async_client.health.weaviate() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.weaviate() @@ -160,6 +177,7 @@ async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None: health = await response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.weaviate() as response: diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index 2d627c70..1a13f77a 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -17,6 +17,7 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_retrieve(self, client: Codex) -> None: organization = client.organizations.retrieve( @@ -24,6 +25,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.organizations.with_raw_response.retrieve( @@ -35,6 +37,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: organization = response.parse() assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.organizations.with_streaming_response.retrieve( @@ -48,6 +51,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -59,6 +63,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: organization = await async_client.organizations.retrieve( @@ -66,6 +71,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.with_raw_response.retrieve( @@ -77,6 +83,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: organization = await response.parse() assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.organizations.with_streaming_response.retrieve( @@ -90,6 +97,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 7e79d8e8..388601d2 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -20,6 +20,7 @@ class TestProjects: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_create(self, client: Codex) -> None: project = client.projects.create( @@ -29,6 +30,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: project = client.projects.create( @@ -39,6 +41,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.with_raw_response.create( @@ -52,6 +55,7 @@ def test_raw_response_create(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.with_streaming_response.create( @@ -67,6 +71,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_retrieve(self, client: Codex) -> None: project = client.projects.retrieve( @@ -74,6 +79,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.with_raw_response.retrieve( @@ -85,6 +91,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.with_streaming_response.retrieve( @@ -98,6 +105,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_update(self, client: Codex) -> None: project = client.projects.update( @@ -107,6 +115,7 @@ def test_method_update(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_method_update_with_all_params(self, client: Codex) -> None: project = client.projects.update( @@ -117,6 +126,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_update(self, client: Codex) -> None: response = client.projects.with_raw_response.update( @@ -130,6 +140,7 @@ def test_raw_response_update(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_update(self, client: Codex) -> None: with client.projects.with_streaming_response.update( @@ -145,6 +156,7 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: project = client.projects.list( @@ -152,6 +164,7 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.with_raw_response.list( @@ -163,6 +176,7 @@ def test_raw_response_list(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.with_streaming_response.list( @@ -176,6 +190,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_delete(self, client: Codex) -> None: project = client.projects.delete( @@ -183,6 +198,7 @@ def test_method_delete(self, client: Codex) -> None: ) assert project is None + @pytest.mark.skip() @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.with_raw_response.delete( @@ -194,6 +210,7 @@ def test_raw_response_delete(self, client: Codex) -> None: project = response.parse() assert project is None + @pytest.mark.skip() @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.with_streaming_response.delete( @@ -207,6 +224,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_export(self, client: Codex) -> None: project = client.projects.export( @@ -214,6 +232,7 @@ def test_method_export(self, client: Codex) -> None: ) assert_matches_type(object, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_export(self, client: Codex) -> None: response = client.projects.with_raw_response.export( @@ -225,6 +244,7 @@ def test_raw_response_export(self, client: Codex) -> None: project = response.parse() assert_matches_type(object, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_export(self, client: Codex) -> None: with client.projects.with_streaming_response.export( @@ -242,6 +262,7 @@ def test_streaming_response_export(self, client: Codex) -> None: class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( @@ -251,6 +272,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( @@ -261,6 +283,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.create( @@ -274,6 +297,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.create( @@ -289,6 +313,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: project = await async_client.projects.retrieve( @@ -296,6 +321,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.retrieve( @@ -307,6 +333,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.retrieve( @@ -320,6 +347,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_update(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( @@ -329,6 +357,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( @@ -339,6 +368,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.update( @@ -352,6 +382,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.update( @@ -367,6 +398,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( @@ -374,6 +406,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.list( @@ -385,6 +418,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.list( @@ -398,6 +432,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: project = await async_client.projects.delete( @@ -405,6 +440,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: ) assert project is None + @pytest.mark.skip() @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.delete( @@ -416,6 +452,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: project = await response.parse() assert project is None + @pytest.mark.skip() @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.delete( @@ -429,6 +466,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_export(self, async_client: AsyncCodex) -> None: project = await async_client.projects.export( @@ -436,6 +474,7 @@ async def test_method_export(self, async_client: AsyncCodex) -> None: ) assert_matches_type(object, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_export(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.export( @@ -447,6 +486,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(object, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_export(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.export( diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index 55bba35f..68996997 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -17,11 +17,13 @@ class TestAPIKey: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_refresh(self, client: Codex) -> None: api_key = client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_refresh(self, client: Codex) -> None: response = client.users.myself.api_key.with_raw_response.refresh() @@ -31,6 +33,7 @@ def test_raw_response_refresh(self, client: Codex) -> None: api_key = response.parse() assert_matches_type(UserSchema, api_key, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_refresh(self, client: Codex) -> None: with client.users.myself.api_key.with_streaming_response.refresh() as response: @@ -46,11 +49,13 @@ def test_streaming_response_refresh(self, client: Codex) -> None: class TestAsyncAPIKey: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_refresh(self, async_client: AsyncCodex) -> None: api_key = await async_client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.api_key.with_raw_response.refresh() @@ -60,6 +65,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: api_key = await response.parse() assert_matches_type(UserSchema, api_key, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.api_key.with_streaming_response.refresh() as response: diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py index da485882..9f35f7c6 100644 --- a/tests/api_resources/users/myself/test_organizations.py +++ b/tests/api_resources/users/myself/test_organizations.py @@ -17,11 +17,13 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: organization = client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.users.myself.organizations.with_raw_response.list() @@ -31,6 +33,7 @@ def test_raw_response_list(self, client: Codex) -> None: organization = response.parse() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.users.myself.organizations.with_streaming_response.list() as response: @@ -46,11 +49,13 @@ def test_streaming_response_list(self, client: Codex) -> None: class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: organization = await async_client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.organizations.with_raw_response.list() @@ -60,6 +65,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: organization = await response.parse() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.organizations.with_streaming_response.list() as response: diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index a206d1f9..63123275 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -17,11 +17,13 @@ class TestMyself: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize def test_method_retrieve(self, client: Codex) -> None: myself = client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.users.myself.with_raw_response.retrieve() @@ -31,6 +33,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: myself = response.parse() assert_matches_type(UserSchemaPublic, myself, path=["response"]) + @pytest.mark.skip() @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.users.myself.with_streaming_response.retrieve() as response: @@ -46,11 +49,13 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: class TestAsyncMyself: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: myself = await async_client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.with_raw_response.retrieve() @@ -60,6 +65,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: myself = await response.parse() assert_matches_type(UserSchemaPublic, myself, path=["response"]) + @pytest.mark.skip() @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.with_streaming_response.retrieve() as response: From caeb663e9711aab43d308968a4df56da30d8a880 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:12:15 +0000 Subject: [PATCH 012/320] feat(api): api update (#7) --- .stats.yml | 4 +- api.md | 12 +- src/codex/resources/projects/__init__.py | 14 - src/codex/resources/projects/knowledge.py | 740 ------------------ src/codex/resources/projects/projects.py | 32 - src/codex/types/projects/__init__.py | 7 - src/codex/types/projects/entry.py | 20 - .../projects/knowledge_add_question_params.py | 11 - .../types/projects/knowledge_create_params.py | 14 - .../types/projects/knowledge_list_params.py | 21 - .../types/projects/knowledge_query_params.py | 11 - .../types/projects/knowledge_update_params.py | 16 - .../types/projects/list_knowledge_response.py | 14 - .../api_resources/projects/test_knowledge.py | 661 ---------------- 14 files changed, 3 insertions(+), 1574 deletions(-) delete mode 100644 src/codex/resources/projects/knowledge.py delete mode 100644 src/codex/types/projects/entry.py delete mode 100644 src/codex/types/projects/knowledge_add_question_params.py delete mode 100644 src/codex/types/projects/knowledge_create_params.py delete mode 100644 src/codex/types/projects/knowledge_list_params.py delete mode 100644 src/codex/types/projects/knowledge_query_params.py delete mode 100644 src/codex/types/projects/knowledge_update_params.py delete mode 100644 src/codex/types/projects/list_knowledge_response.py delete mode 100644 tests/api_resources/projects/test_knowledge.py diff --git a/.stats.yml b/.stats.yml index a156c044..f56fed6e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 29 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-5691cf2ac1617d7acc6532acfb2c08e151b48e6b045cdaf1715ff3843f611eb0.yml +configured_endpoints: 22 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-b706b59cb40e75ffd920fc42483227383f9ef24ecb1cb1eb5f9424b388997444.yml diff --git a/api.md b/api.md index 672aeeb6..ff5b8fd3 100644 --- a/api.md +++ b/api.md @@ -116,15 +116,5 @@ Methods: Types: ```python -from codex.types.projects import Entry, ListKnowledgeResponse +from codex.types.projects import Entry ``` - -Methods: - -- client.projects.knowledge.create(project_id, \*\*params) -> Entry -- client.projects.knowledge.retrieve(entry_id, \*, project_id) -> Entry -- client.projects.knowledge.update(entry_id, \*, project_id, \*\*params) -> Entry -- client.projects.knowledge.list(project_id, \*\*params) -> ListKnowledgeResponse -- client.projects.knowledge.delete(entry_id, \*, project_id) -> None -- client.projects.knowledge.add_question(project_id, \*\*params) -> Entry -- client.projects.knowledge.query(project_id, \*\*params) -> Optional[Entry] diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py index 5250b0b3..9c31620c 100644 --- a/src/codex/resources/projects/__init__.py +++ b/src/codex/resources/projects/__init__.py @@ -8,14 +8,6 @@ ProjectsResourceWithStreamingResponse, AsyncProjectsResourceWithStreamingResponse, ) -from .knowledge import ( - KnowledgeResource, - AsyncKnowledgeResource, - KnowledgeResourceWithRawResponse, - AsyncKnowledgeResourceWithRawResponse, - KnowledgeResourceWithStreamingResponse, - AsyncKnowledgeResourceWithStreamingResponse, -) from .access_keys import ( AccessKeysResource, AsyncAccessKeysResource, @@ -32,12 +24,6 @@ "AsyncAccessKeysResourceWithRawResponse", "AccessKeysResourceWithStreamingResponse", "AsyncAccessKeysResourceWithStreamingResponse", - "KnowledgeResource", - "AsyncKnowledgeResource", - "KnowledgeResourceWithRawResponse", - "AsyncKnowledgeResourceWithRawResponse", - "KnowledgeResourceWithStreamingResponse", - "AsyncKnowledgeResourceWithStreamingResponse", "ProjectsResource", "AsyncProjectsResource", "ProjectsResourceWithRawResponse", diff --git a/src/codex/resources/projects/knowledge.py b/src/codex/resources/projects/knowledge.py deleted file mode 100644 index 9c43c669..00000000 --- a/src/codex/resources/projects/knowledge.py +++ /dev/null @@ -1,740 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Literal - -import httpx - -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -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.projects import ( - knowledge_list_params, - knowledge_query_params, - knowledge_create_params, - knowledge_update_params, - knowledge_add_question_params, -) -from ...types.projects.entry import Entry -from ...types.projects.list_knowledge_response import ListKnowledgeResponse - -__all__ = ["KnowledgeResource", "AsyncKnowledgeResource"] - - -class KnowledgeResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> KnowledgeResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return KnowledgeResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> KnowledgeResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return KnowledgeResourceWithStreamingResponse(self) - - def create( - self, - project_id: int, - *, - question: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Create a knowledge entry for a project. - - 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._post( - f"/api/projects/{project_id}/knowledge/", - body=maybe_transform( - { - "question": question, - "answer": answer, - }, - knowledge_create_params.KnowledgeCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - def retrieve( - self, - entry_id: str, - *, - project_id: int, - # 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, - ) -> Entry: - """ - Get a knowledge entry for a project. - - 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 entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return self._get( - f"/api/projects/{project_id}/knowledge/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - def update( - self, - entry_id: str, - *, - project_id: int, - answer: Optional[str] | NotGiven = NOT_GIVEN, - question: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Update a knowledge entry for a project. - - 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 entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return self._put( - f"/api/projects/{project_id}/knowledge/{entry_id}", - body=maybe_transform( - { - "answer": answer, - "question": question, - }, - knowledge_update_params.KnowledgeUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - def list( - self, - project_id: int, - *, - answered_only: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, - unanswered_only: bool | NotGiven = NOT_GIVEN, - # 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, - ) -> ListKnowledgeResponse: - """ - List knowledge entries for a project. - - 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( - f"/api/projects/{project_id}/knowledge/", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "answered_only": answered_only, - "limit": limit, - "offset": offset, - "order": order, - "sort": sort, - "unanswered_only": unanswered_only, - }, - knowledge_list_params.KnowledgeListParams, - ), - ), - cast_to=ListKnowledgeResponse, - ) - - def delete( - self, - entry_id: str, - *, - project_id: int, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> None: - """ - Delete a knowledge entry for a project. - - 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 entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._delete( - f"/api/projects/{project_id}/knowledge/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - def add_question( - self, - project_id: int, - *, - question: 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, - ) -> Entry: - """ - Add a question to a project. - - Returns: 201 Created if a new question was added 200 OK if the question already - existed - - 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._post( - f"/api/projects/{project_id}/knowledge/add_question", - body=maybe_transform({"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - def query( - self, - project_id: int, - *, - question: 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, - ) -> Optional[Entry]: - """ - Query knowledge for a project. - - Returns the matching entry if found and answered, otherwise returns None. This - allows the client to distinguish between: (1) no matching question found - (returns None), and (2) matching question found but not yet answered (returns - Entry with answer=None). - - 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._post( - f"/api/projects/{project_id}/knowledge/query", - body=maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - -class AsyncKnowledgeResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncKnowledgeResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return AsyncKnowledgeResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncKnowledgeResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return AsyncKnowledgeResourceWithStreamingResponse(self) - - async def create( - self, - project_id: int, - *, - question: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Create a knowledge entry for a project. - - 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._post( - f"/api/projects/{project_id}/knowledge/", - body=await async_maybe_transform( - { - "question": question, - "answer": answer, - }, - knowledge_create_params.KnowledgeCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - async def retrieve( - self, - entry_id: str, - *, - project_id: int, - # 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, - ) -> Entry: - """ - Get a knowledge entry for a project. - - 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 entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return await self._get( - f"/api/projects/{project_id}/knowledge/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - async def update( - self, - entry_id: str, - *, - project_id: int, - answer: Optional[str] | NotGiven = NOT_GIVEN, - question: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Update a knowledge entry for a project. - - 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 entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return await self._put( - f"/api/projects/{project_id}/knowledge/{entry_id}", - body=await async_maybe_transform( - { - "answer": answer, - "question": question, - }, - knowledge_update_params.KnowledgeUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - async def list( - self, - project_id: int, - *, - answered_only: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, - unanswered_only: bool | NotGiven = NOT_GIVEN, - # 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, - ) -> ListKnowledgeResponse: - """ - List knowledge entries for a project. - - 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( - f"/api/projects/{project_id}/knowledge/", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "answered_only": answered_only, - "limit": limit, - "offset": offset, - "order": order, - "sort": sort, - "unanswered_only": unanswered_only, - }, - knowledge_list_params.KnowledgeListParams, - ), - ), - cast_to=ListKnowledgeResponse, - ) - - async def delete( - self, - entry_id: str, - *, - project_id: int, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> None: - """ - Delete a knowledge entry for a project. - - 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 entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return await self._delete( - f"/api/projects/{project_id}/knowledge/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - async def add_question( - self, - project_id: int, - *, - question: 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, - ) -> Entry: - """ - Add a question to a project. - - Returns: 201 Created if a new question was added 200 OK if the question already - existed - - 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._post( - f"/api/projects/{project_id}/knowledge/add_question", - body=await async_maybe_transform( - {"question": question}, knowledge_add_question_params.KnowledgeAddQuestionParams - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - async def query( - self, - project_id: int, - *, - question: 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, - ) -> Optional[Entry]: - """ - Query knowledge for a project. - - Returns the matching entry if found and answered, otherwise returns None. This - allows the client to distinguish between: (1) no matching question found - (returns None), and (2) matching question found but not yet answered (returns - Entry with answer=None). - - 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._post( - f"/api/projects/{project_id}/knowledge/query", - body=await async_maybe_transform({"question": question}, knowledge_query_params.KnowledgeQueryParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - -class KnowledgeResourceWithRawResponse: - def __init__(self, knowledge: KnowledgeResource) -> None: - self._knowledge = knowledge - - self.create = to_raw_response_wrapper( - knowledge.create, - ) - self.retrieve = to_raw_response_wrapper( - knowledge.retrieve, - ) - self.update = to_raw_response_wrapper( - knowledge.update, - ) - self.list = to_raw_response_wrapper( - knowledge.list, - ) - self.delete = to_raw_response_wrapper( - knowledge.delete, - ) - self.add_question = to_raw_response_wrapper( - knowledge.add_question, - ) - self.query = to_raw_response_wrapper( - knowledge.query, - ) - - -class AsyncKnowledgeResourceWithRawResponse: - def __init__(self, knowledge: AsyncKnowledgeResource) -> None: - self._knowledge = knowledge - - self.create = async_to_raw_response_wrapper( - knowledge.create, - ) - self.retrieve = async_to_raw_response_wrapper( - knowledge.retrieve, - ) - self.update = async_to_raw_response_wrapper( - knowledge.update, - ) - self.list = async_to_raw_response_wrapper( - knowledge.list, - ) - self.delete = async_to_raw_response_wrapper( - knowledge.delete, - ) - self.add_question = async_to_raw_response_wrapper( - knowledge.add_question, - ) - self.query = async_to_raw_response_wrapper( - knowledge.query, - ) - - -class KnowledgeResourceWithStreamingResponse: - def __init__(self, knowledge: KnowledgeResource) -> None: - self._knowledge = knowledge - - self.create = to_streamed_response_wrapper( - knowledge.create, - ) - self.retrieve = to_streamed_response_wrapper( - knowledge.retrieve, - ) - self.update = to_streamed_response_wrapper( - knowledge.update, - ) - self.list = to_streamed_response_wrapper( - knowledge.list, - ) - self.delete = to_streamed_response_wrapper( - knowledge.delete, - ) - self.add_question = to_streamed_response_wrapper( - knowledge.add_question, - ) - self.query = to_streamed_response_wrapper( - knowledge.query, - ) - - -class AsyncKnowledgeResourceWithStreamingResponse: - def __init__(self, knowledge: AsyncKnowledgeResource) -> None: - self._knowledge = knowledge - - self.create = async_to_streamed_response_wrapper( - knowledge.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - knowledge.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - knowledge.update, - ) - self.list = async_to_streamed_response_wrapper( - knowledge.list, - ) - self.delete = async_to_streamed_response_wrapper( - knowledge.delete, - ) - self.add_question = async_to_streamed_response_wrapper( - knowledge.add_question, - ) - self.query = async_to_streamed_response_wrapper( - knowledge.query, - ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 845669a8..a7bea419 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -13,14 +13,6 @@ async_maybe_transform, ) from ..._compat import cached_property -from .knowledge import ( - KnowledgeResource, - AsyncKnowledgeResource, - KnowledgeResourceWithRawResponse, - AsyncKnowledgeResourceWithRawResponse, - KnowledgeResourceWithStreamingResponse, - AsyncKnowledgeResourceWithStreamingResponse, -) from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( to_raw_response_wrapper, @@ -48,10 +40,6 @@ class ProjectsResource(SyncAPIResource): def access_keys(self) -> AccessKeysResource: return AccessKeysResource(self._client) - @cached_property - def knowledge(self) -> KnowledgeResource: - return KnowledgeResource(self._client) - @cached_property def with_raw_response(self) -> ProjectsResourceWithRawResponse: """ @@ -291,10 +279,6 @@ class AsyncProjectsResource(AsyncAPIResource): def access_keys(self) -> AsyncAccessKeysResource: return AsyncAccessKeysResource(self._client) - @cached_property - def knowledge(self) -> AsyncKnowledgeResource: - return AsyncKnowledgeResource(self._client) - @cached_property def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: """ @@ -558,10 +542,6 @@ def __init__(self, projects: ProjectsResource) -> None: def access_keys(self) -> AccessKeysResourceWithRawResponse: return AccessKeysResourceWithRawResponse(self._projects.access_keys) - @cached_property - def knowledge(self) -> KnowledgeResourceWithRawResponse: - return KnowledgeResourceWithRawResponse(self._projects.knowledge) - class AsyncProjectsResourceWithRawResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -590,10 +570,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None: def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys) - @cached_property - def knowledge(self) -> AsyncKnowledgeResourceWithRawResponse: - return AsyncKnowledgeResourceWithRawResponse(self._projects.knowledge) - class ProjectsResourceWithStreamingResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -622,10 +598,6 @@ def __init__(self, projects: ProjectsResource) -> None: def access_keys(self) -> AccessKeysResourceWithStreamingResponse: return AccessKeysResourceWithStreamingResponse(self._projects.access_keys) - @cached_property - def knowledge(self) -> KnowledgeResourceWithStreamingResponse: - return KnowledgeResourceWithStreamingResponse(self._projects.knowledge) - class AsyncProjectsResourceWithStreamingResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -653,7 +625,3 @@ def __init__(self, projects: AsyncProjectsResource) -> None: @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys) - - @cached_property - def knowledge(self) -> AsyncKnowledgeResourceWithStreamingResponse: - return AsyncKnowledgeResourceWithStreamingResponse(self._projects.knowledge) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 9813f780..7aadc5f0 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -2,17 +2,10 @@ from __future__ import annotations -from .entry import Entry as Entry from .access_key_schema import AccessKeySchema as AccessKeySchema -from .knowledge_list_params import KnowledgeListParams as KnowledgeListParams -from .knowledge_query_params import KnowledgeQueryParams as KnowledgeQueryParams -from .knowledge_create_params import KnowledgeCreateParams as KnowledgeCreateParams -from .knowledge_update_params import KnowledgeUpdateParams as KnowledgeUpdateParams -from .list_knowledge_response import ListKnowledgeResponse as ListKnowledgeResponse from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams -from .knowledge_add_question_params import KnowledgeAddQuestionParams as KnowledgeAddQuestionParams from .access_key_retrieve_project_id_response import ( AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse, ) diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py deleted file mode 100644 index bfa7d278..00000000 --- a/src/codex/types/projects/entry.py +++ /dev/null @@ -1,20 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from ..._models import BaseModel - -__all__ = ["Entry"] - - -class Entry(BaseModel): - id: str - - created_at: datetime - - question: str - - answer: Optional[str] = None - - answered_at: Optional[datetime] = None diff --git a/src/codex/types/projects/knowledge_add_question_params.py b/src/codex/types/projects/knowledge_add_question_params.py deleted file mode 100644 index 97115554..00000000 --- a/src/codex/types/projects/knowledge_add_question_params.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -__all__ = ["KnowledgeAddQuestionParams"] - - -class KnowledgeAddQuestionParams(TypedDict, total=False): - question: Required[str] diff --git a/src/codex/types/projects/knowledge_create_params.py b/src/codex/types/projects/knowledge_create_params.py deleted file mode 100644 index 9c544e6e..00000000 --- a/src/codex/types/projects/knowledge_create_params.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Required, TypedDict - -__all__ = ["KnowledgeCreateParams"] - - -class KnowledgeCreateParams(TypedDict, total=False): - question: Required[str] - - answer: Optional[str] diff --git a/src/codex/types/projects/knowledge_list_params.py b/src/codex/types/projects/knowledge_list_params.py deleted file mode 100644 index 45aee13e..00000000 --- a/src/codex/types/projects/knowledge_list_params.py +++ /dev/null @@ -1,21 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypedDict - -__all__ = ["KnowledgeListParams"] - - -class KnowledgeListParams(TypedDict, total=False): - answered_only: bool - - limit: int - - offset: int - - order: Literal["asc", "desc"] - - sort: Literal["created_at", "answered_at"] - - unanswered_only: bool diff --git a/src/codex/types/projects/knowledge_query_params.py b/src/codex/types/projects/knowledge_query_params.py deleted file mode 100644 index 2c8097af..00000000 --- a/src/codex/types/projects/knowledge_query_params.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -__all__ = ["KnowledgeQueryParams"] - - -class KnowledgeQueryParams(TypedDict, total=False): - question: Required[str] diff --git a/src/codex/types/projects/knowledge_update_params.py b/src/codex/types/projects/knowledge_update_params.py deleted file mode 100644 index b5e22269..00000000 --- a/src/codex/types/projects/knowledge_update_params.py +++ /dev/null @@ -1,16 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Required, TypedDict - -__all__ = ["KnowledgeUpdateParams"] - - -class KnowledgeUpdateParams(TypedDict, total=False): - project_id: Required[int] - - answer: Optional[str] - - question: Optional[str] diff --git a/src/codex/types/projects/list_knowledge_response.py b/src/codex/types/projects/list_knowledge_response.py deleted file mode 100644 index d32f4e38..00000000 --- a/src/codex/types/projects/list_knowledge_response.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from .entry import Entry -from ..._models import BaseModel - -__all__ = ["ListKnowledgeResponse"] - - -class ListKnowledgeResponse(BaseModel): - entries: List[Entry] - - total_count: int diff --git a/tests/api_resources/projects/test_knowledge.py b/tests/api_resources/projects/test_knowledge.py deleted file mode 100644 index ac538743..00000000 --- a/tests/api_resources/projects/test_knowledge.py +++ /dev/null @@ -1,661 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, Optional, cast - -import pytest - -from codex import Codex, AsyncCodex -from tests.utils import assert_matches_type -from codex.types.projects import ( - Entry, - ListKnowledgeResponse, -) - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestKnowledge: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - def test_method_create(self, client: Codex) -> None: - knowledge = client.projects.knowledge.create( - project_id=0, - question="question", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_create_with_all_params(self, client: Codex) -> None: - knowledge = client.projects.knowledge.create( - project_id=0, - question="question", - answer="answer", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_create(self, client: Codex) -> None: - response = client.projects.knowledge.with_raw_response.create( - project_id=0, - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_create(self, client: Codex) -> None: - with client.projects.knowledge.with_streaming_response.create( - project_id=0, - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_method_retrieve(self, client: Codex) -> None: - knowledge = client.projects.knowledge.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_retrieve(self, client: Codex) -> None: - response = client.projects.knowledge.with_raw_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_retrieve(self, client: Codex) -> None: - with client.projects.knowledge.with_streaming_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_retrieve(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.knowledge.with_raw_response.retrieve( - entry_id="", - project_id=0, - ) - - @pytest.mark.skip() - @parametrize - def test_method_update(self, client: Codex) -> None: - knowledge = client.projects.knowledge.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_update_with_all_params(self, client: Codex) -> None: - knowledge = client.projects.knowledge.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - answer="answer", - question="question", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_update(self, client: Codex) -> None: - response = client.projects.knowledge.with_raw_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_update(self, client: Codex) -> None: - with client.projects.knowledge.with_streaming_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_update(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.knowledge.with_raw_response.update( - entry_id="", - project_id=0, - ) - - @pytest.mark.skip() - @parametrize - def test_method_list(self, client: Codex) -> None: - knowledge = client.projects.knowledge.list( - project_id=0, - ) - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_list_with_all_params(self, client: Codex) -> None: - knowledge = client.projects.knowledge.list( - project_id=0, - answered_only=True, - limit=1, - offset=0, - order="asc", - sort="created_at", - unanswered_only=True, - ) - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.knowledge.with_raw_response.list( - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = response.parse() - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.knowledge.with_streaming_response.list( - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = response.parse() - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_method_delete(self, client: Codex) -> None: - knowledge = client.projects.knowledge.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - assert knowledge is None - - @pytest.mark.skip() - @parametrize - def test_raw_response_delete(self, client: Codex) -> None: - response = client.projects.knowledge.with_raw_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = response.parse() - assert knowledge is None - - @pytest.mark.skip() - @parametrize - def test_streaming_response_delete(self, client: Codex) -> None: - with client.projects.knowledge.with_streaming_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = response.parse() - assert knowledge is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_delete(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.knowledge.with_raw_response.delete( - entry_id="", - project_id=0, - ) - - @pytest.mark.skip() - @parametrize - def test_method_add_question(self, client: Codex) -> None: - knowledge = client.projects.knowledge.add_question( - project_id=0, - question="question", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_add_question(self, client: Codex) -> None: - response = client.projects.knowledge.with_raw_response.add_question( - project_id=0, - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_add_question(self, client: Codex) -> None: - with client.projects.knowledge.with_streaming_response.add_question( - project_id=0, - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_method_query(self, client: Codex) -> None: - knowledge = client.projects.knowledge.query( - project_id=0, - question="question", - ) - assert_matches_type(Optional[Entry], knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_query(self, client: Codex) -> None: - response = client.projects.knowledge.with_raw_response.query( - project_id=0, - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = response.parse() - assert_matches_type(Optional[Entry], knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_query(self, client: Codex) -> None: - with client.projects.knowledge.with_streaming_response.query( - project_id=0, - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = response.parse() - assert_matches_type(Optional[Entry], knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncKnowledge: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - async def test_method_create(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.create( - project_id=0, - question="question", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.create( - project_id=0, - question="question", - answer="answer", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_create(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.knowledge.with_raw_response.create( - project_id=0, - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: - async with async_client.projects.knowledge.with_streaming_response.create( - project_id=0, - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_method_retrieve(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.knowledge.with_raw_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: - async with async_client.projects.knowledge.with_streaming_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.knowledge.with_raw_response.retrieve( - entry_id="", - project_id=0, - ) - - @pytest.mark.skip() - @parametrize - async def test_method_update(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - answer="answer", - question="question", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_update(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.knowledge.with_raw_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: - async with async_client.projects.knowledge.with_streaming_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_update(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.knowledge.with_raw_response.update( - entry_id="", - project_id=0, - ) - - @pytest.mark.skip() - @parametrize - async def test_method_list(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.list( - project_id=0, - ) - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.list( - project_id=0, - answered_only=True, - limit=1, - offset=0, - order="asc", - sort="created_at", - unanswered_only=True, - ) - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.knowledge.with_raw_response.list( - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = await response.parse() - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.knowledge.with_streaming_response.list( - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = await response.parse() - assert_matches_type(ListKnowledgeResponse, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_method_delete(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - assert knowledge is None - - @pytest.mark.skip() - @parametrize - async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.knowledge.with_raw_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = await response.parse() - assert knowledge is None - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: - async with async_client.projects.knowledge.with_streaming_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = await response.parse() - assert knowledge is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_delete(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.knowledge.with_raw_response.delete( - entry_id="", - project_id=0, - ) - - @pytest.mark.skip() - @parametrize - async def test_method_add_question(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.add_question( - project_id=0, - question="question", - ) - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.knowledge.with_raw_response.add_question( - project_id=0, - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: - async with async_client.projects.knowledge.with_streaming_response.add_question( - project_id=0, - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = await response.parse() - assert_matches_type(Entry, knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_method_query(self, async_client: AsyncCodex) -> None: - knowledge = await async_client.projects.knowledge.query( - project_id=0, - question="question", - ) - assert_matches_type(Optional[Entry], knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_query(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.knowledge.with_raw_response.query( - project_id=0, - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - knowledge = await response.parse() - assert_matches_type(Optional[Entry], knowledge, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: - async with async_client.projects.knowledge.with_streaming_response.query( - project_id=0, - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - knowledge = await response.parse() - assert_matches_type(Optional[Entry], knowledge, path=["response"]) - - assert cast(Any, response.is_closed) is True From c61fcce3f6e67b8032b58d62b4513d33628093af Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:14:04 +0000 Subject: [PATCH 013/320] feat: update openapi spec (#8) --- .stats.yml | 2 +- api.md | 14 +- src/codex/resources/projects/__init__.py | 14 + src/codex/resources/projects/entries.py | 738 ++++++++++++++++++ src/codex/resources/projects/projects.py | 32 + src/codex/types/projects/__init__.py | 7 + src/codex/types/projects/entry.py | 20 + .../projects/entry_add_question_params.py | 11 + .../types/projects/entry_create_params.py | 14 + src/codex/types/projects/entry_list_params.py | 21 + .../types/projects/entry_list_response.py | 14 + .../types/projects/entry_query_params.py | 11 + .../types/projects/entry_update_params.py | 16 + tests/api_resources/projects/test_entries.py | 661 ++++++++++++++++ 14 files changed, 1572 insertions(+), 3 deletions(-) create mode 100644 src/codex/resources/projects/entries.py create mode 100644 src/codex/types/projects/entry.py create mode 100644 src/codex/types/projects/entry_add_question_params.py create mode 100644 src/codex/types/projects/entry_create_params.py create mode 100644 src/codex/types/projects/entry_list_params.py create mode 100644 src/codex/types/projects/entry_list_response.py create mode 100644 src/codex/types/projects/entry_query_params.py create mode 100644 src/codex/types/projects/entry_update_params.py create mode 100644 tests/api_resources/projects/test_entries.py diff --git a/.stats.yml b/.stats.yml index f56fed6e..66033eb6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 22 +configured_endpoints: 29 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-b706b59cb40e75ffd920fc42483227383f9ef24ecb1cb1eb5f9424b388997444.yml diff --git a/api.md b/api.md index ff5b8fd3..3665b5ba 100644 --- a/api.md +++ b/api.md @@ -111,10 +111,20 @@ Methods: - client.projects.access_keys.retrieve_project_id() -> AccessKeyRetrieveProjectIDResponse - client.projects.access_keys.revoke(access_key_id, \*, project_id) -> None -## Knowledge +## Entries Types: ```python -from codex.types.projects import Entry +from codex.types.projects import Entry, EntryListResponse ``` + +Methods: + +- client.projects.entries.create(project_id, \*\*params) -> Entry +- client.projects.entries.retrieve(entry_id, \*, project_id) -> Entry +- client.projects.entries.update(entry_id, \*, project_id, \*\*params) -> Entry +- client.projects.entries.list(project_id, \*\*params) -> EntryListResponse +- client.projects.entries.delete(entry_id, \*, project_id) -> None +- client.projects.entries.add_question(project_id, \*\*params) -> Entry +- client.projects.entries.query(project_id, \*\*params) -> Optional[Entry] diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py index 9c31620c..01599858 100644 --- a/src/codex/resources/projects/__init__.py +++ b/src/codex/resources/projects/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .entries import ( + EntriesResource, + AsyncEntriesResource, + EntriesResourceWithRawResponse, + AsyncEntriesResourceWithRawResponse, + EntriesResourceWithStreamingResponse, + AsyncEntriesResourceWithStreamingResponse, +) from .projects import ( ProjectsResource, AsyncProjectsResource, @@ -24,6 +32,12 @@ "AsyncAccessKeysResourceWithRawResponse", "AccessKeysResourceWithStreamingResponse", "AsyncAccessKeysResourceWithStreamingResponse", + "EntriesResource", + "AsyncEntriesResource", + "EntriesResourceWithRawResponse", + "AsyncEntriesResourceWithRawResponse", + "EntriesResourceWithStreamingResponse", + "AsyncEntriesResourceWithStreamingResponse", "ProjectsResource", "AsyncProjectsResource", "ProjectsResourceWithRawResponse", diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py new file mode 100644 index 00000000..6121842e --- /dev/null +++ b/src/codex/resources/projects/entries.py @@ -0,0 +1,738 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +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.projects import ( + entry_list_params, + entry_query_params, + entry_create_params, + entry_update_params, + entry_add_question_params, +) +from ...types.projects.entry import Entry +from ...types.projects.entry_list_response import EntryListResponse + +__all__ = ["EntriesResource", "AsyncEntriesResource"] + + +class EntriesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EntriesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return EntriesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EntriesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return EntriesResourceWithStreamingResponse(self) + + def create( + self, + project_id: int, + *, + question: str, + answer: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Create a knowledge entry for a project. + + 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._post( + f"/api/projects/{project_id}/entries/", + body=maybe_transform( + { + "question": question, + "answer": answer, + }, + entry_create_params.EntryCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def retrieve( + self, + entry_id: str, + *, + project_id: int, + # 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, + ) -> Entry: + """ + Get a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._get( + f"/api/projects/{project_id}/entries/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def update( + self, + entry_id: str, + *, + project_id: int, + answer: Optional[str] | NotGiven = NOT_GIVEN, + question: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Update a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._put( + f"/api/projects/{project_id}/entries/{entry_id}", + body=maybe_transform( + { + "answer": answer, + "question": question, + }, + entry_update_params.EntryUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def list( + self, + project_id: int, + *, + answered_only: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, + unanswered_only: bool | NotGiven = NOT_GIVEN, + # 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, + ) -> EntryListResponse: + """ + List knowledge entries for a project. + + 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( + f"/api/projects/{project_id}/entries/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "answered_only": answered_only, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "unanswered_only": unanswered_only, + }, + entry_list_params.EntryListParams, + ), + ), + cast_to=EntryListResponse, + ) + + def delete( + self, + entry_id: str, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/api/projects/{project_id}/entries/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def add_question( + self, + project_id: int, + *, + question: 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, + ) -> Entry: + """ + Add a question to a project. + + Returns: 201 Created if a new question was added 200 OK if the question already + existed + + 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._post( + f"/api/projects/{project_id}/entries/add_question", + body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + def query( + self, + project_id: int, + *, + question: 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, + ) -> Optional[Entry]: + """ + Query knowledge for a project. + + Returns the matching entry if found and answered, otherwise returns None. This + allows the client to distinguish between: (1) no matching question found + (returns None), and (2) matching question found but not yet answered (returns + Entry with answer=None). + + 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._post( + f"/api/projects/{project_id}/entries/query", + body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + +class AsyncEntriesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEntriesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncEntriesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEntriesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncEntriesResourceWithStreamingResponse(self) + + async def create( + self, + project_id: int, + *, + question: str, + answer: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Create a knowledge entry for a project. + + 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._post( + f"/api/projects/{project_id}/entries/", + body=await async_maybe_transform( + { + "question": question, + "answer": answer, + }, + entry_create_params.EntryCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def retrieve( + self, + entry_id: str, + *, + project_id: int, + # 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, + ) -> Entry: + """ + Get a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._get( + f"/api/projects/{project_id}/entries/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def update( + self, + entry_id: str, + *, + project_id: int, + answer: Optional[str] | NotGiven = NOT_GIVEN, + question: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> Entry: + """ + Update a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._put( + f"/api/projects/{project_id}/entries/{entry_id}", + body=await async_maybe_transform( + { + "answer": answer, + "question": question, + }, + entry_update_params.EntryUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def list( + self, + project_id: int, + *, + answered_only: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, + unanswered_only: bool | NotGiven = NOT_GIVEN, + # 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, + ) -> EntryListResponse: + """ + List knowledge entries for a project. + + 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( + f"/api/projects/{project_id}/entries/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "answered_only": answered_only, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "unanswered_only": unanswered_only, + }, + entry_list_params.EntryListParams, + ), + ), + cast_to=EntryListResponse, + ) + + async def delete( + self, + entry_id: str, + *, + project_id: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete a knowledge entry for a project. + + 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 entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/api/projects/{project_id}/entries/{entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def add_question( + self, + project_id: int, + *, + question: 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, + ) -> Entry: + """ + Add a question to a project. + + Returns: 201 Created if a new question was added 200 OK if the question already + existed + + 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._post( + f"/api/projects/{project_id}/entries/add_question", + body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + async def query( + self, + project_id: int, + *, + question: 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, + ) -> Optional[Entry]: + """ + Query knowledge for a project. + + Returns the matching entry if found and answered, otherwise returns None. This + allows the client to distinguish between: (1) no matching question found + (returns None), and (2) matching question found but not yet answered (returns + Entry with answer=None). + + 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._post( + f"/api/projects/{project_id}/entries/query", + body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + + +class EntriesResourceWithRawResponse: + def __init__(self, entries: EntriesResource) -> None: + self._entries = entries + + self.create = to_raw_response_wrapper( + entries.create, + ) + self.retrieve = to_raw_response_wrapper( + entries.retrieve, + ) + self.update = to_raw_response_wrapper( + entries.update, + ) + self.list = to_raw_response_wrapper( + entries.list, + ) + self.delete = to_raw_response_wrapper( + entries.delete, + ) + self.add_question = to_raw_response_wrapper( + entries.add_question, + ) + self.query = to_raw_response_wrapper( + entries.query, + ) + + +class AsyncEntriesResourceWithRawResponse: + def __init__(self, entries: AsyncEntriesResource) -> None: + self._entries = entries + + self.create = async_to_raw_response_wrapper( + entries.create, + ) + self.retrieve = async_to_raw_response_wrapper( + entries.retrieve, + ) + self.update = async_to_raw_response_wrapper( + entries.update, + ) + self.list = async_to_raw_response_wrapper( + entries.list, + ) + self.delete = async_to_raw_response_wrapper( + entries.delete, + ) + self.add_question = async_to_raw_response_wrapper( + entries.add_question, + ) + self.query = async_to_raw_response_wrapper( + entries.query, + ) + + +class EntriesResourceWithStreamingResponse: + def __init__(self, entries: EntriesResource) -> None: + self._entries = entries + + self.create = to_streamed_response_wrapper( + entries.create, + ) + self.retrieve = to_streamed_response_wrapper( + entries.retrieve, + ) + self.update = to_streamed_response_wrapper( + entries.update, + ) + self.list = to_streamed_response_wrapper( + entries.list, + ) + self.delete = to_streamed_response_wrapper( + entries.delete, + ) + self.add_question = to_streamed_response_wrapper( + entries.add_question, + ) + self.query = to_streamed_response_wrapper( + entries.query, + ) + + +class AsyncEntriesResourceWithStreamingResponse: + def __init__(self, entries: AsyncEntriesResource) -> None: + self._entries = entries + + self.create = async_to_streamed_response_wrapper( + entries.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + entries.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + entries.update, + ) + self.list = async_to_streamed_response_wrapper( + entries.list, + ) + self.delete = async_to_streamed_response_wrapper( + entries.delete, + ) + self.add_question = async_to_streamed_response_wrapper( + entries.add_question, + ) + self.query = async_to_streamed_response_wrapper( + entries.query, + ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index a7bea419..d77e63a1 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -7,6 +7,14 @@ import httpx from ...types import project_list_params, project_create_params, project_update_params +from .entries import ( + EntriesResource, + AsyncEntriesResource, + EntriesResourceWithRawResponse, + AsyncEntriesResourceWithRawResponse, + EntriesResourceWithStreamingResponse, + AsyncEntriesResourceWithStreamingResponse, +) from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven from ..._utils import ( maybe_transform, @@ -40,6 +48,10 @@ class ProjectsResource(SyncAPIResource): def access_keys(self) -> AccessKeysResource: return AccessKeysResource(self._client) + @cached_property + def entries(self) -> EntriesResource: + return EntriesResource(self._client) + @cached_property def with_raw_response(self) -> ProjectsResourceWithRawResponse: """ @@ -279,6 +291,10 @@ class AsyncProjectsResource(AsyncAPIResource): def access_keys(self) -> AsyncAccessKeysResource: return AsyncAccessKeysResource(self._client) + @cached_property + def entries(self) -> AsyncEntriesResource: + return AsyncEntriesResource(self._client) + @cached_property def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: """ @@ -542,6 +558,10 @@ def __init__(self, projects: ProjectsResource) -> None: def access_keys(self) -> AccessKeysResourceWithRawResponse: return AccessKeysResourceWithRawResponse(self._projects.access_keys) + @cached_property + def entries(self) -> EntriesResourceWithRawResponse: + return EntriesResourceWithRawResponse(self._projects.entries) + class AsyncProjectsResourceWithRawResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -570,6 +590,10 @@ def __init__(self, projects: AsyncProjectsResource) -> None: def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys) + @cached_property + def entries(self) -> AsyncEntriesResourceWithRawResponse: + return AsyncEntriesResourceWithRawResponse(self._projects.entries) + class ProjectsResourceWithStreamingResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -598,6 +622,10 @@ def __init__(self, projects: ProjectsResource) -> None: def access_keys(self) -> AccessKeysResourceWithStreamingResponse: return AccessKeysResourceWithStreamingResponse(self._projects.access_keys) + @cached_property + def entries(self) -> EntriesResourceWithStreamingResponse: + return EntriesResourceWithStreamingResponse(self._projects.entries) + class AsyncProjectsResourceWithStreamingResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -625,3 +653,7 @@ def __init__(self, projects: AsyncProjectsResource) -> None: @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys) + + @cached_property + def entries(self) -> AsyncEntriesResourceWithStreamingResponse: + return AsyncEntriesResourceWithStreamingResponse(self._projects.entries) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 7aadc5f0..b79e23f2 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -2,10 +2,17 @@ from __future__ import annotations +from .entry import Entry as Entry from .access_key_schema import AccessKeySchema as AccessKeySchema +from .entry_list_params import EntryListParams as EntryListParams +from .entry_query_params import EntryQueryParams as EntryQueryParams +from .entry_create_params import EntryCreateParams as EntryCreateParams +from .entry_list_response import EntryListResponse as EntryListResponse +from .entry_update_params import EntryUpdateParams as EntryUpdateParams from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams +from .entry_add_question_params import EntryAddQuestionParams as EntryAddQuestionParams from .access_key_retrieve_project_id_response import ( AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse, ) diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py new file mode 100644 index 00000000..bfa7d278 --- /dev/null +++ b/src/codex/types/projects/entry.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ..._models import BaseModel + +__all__ = ["Entry"] + + +class Entry(BaseModel): + id: str + + created_at: datetime + + question: str + + answer: Optional[str] = None + + answered_at: Optional[datetime] = None diff --git a/src/codex/types/projects/entry_add_question_params.py b/src/codex/types/projects/entry_add_question_params.py new file mode 100644 index 00000000..e2d009b4 --- /dev/null +++ b/src/codex/types/projects/entry_add_question_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["EntryAddQuestionParams"] + + +class EntryAddQuestionParams(TypedDict, total=False): + question: Required[str] diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py new file mode 100644 index 00000000..1ac23dd4 --- /dev/null +++ b/src/codex/types/projects/entry_create_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["EntryCreateParams"] + + +class EntryCreateParams(TypedDict, total=False): + question: Required[str] + + answer: Optional[str] diff --git a/src/codex/types/projects/entry_list_params.py b/src/codex/types/projects/entry_list_params.py new file mode 100644 index 00000000..b50181f7 --- /dev/null +++ b/src/codex/types/projects/entry_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["EntryListParams"] + + +class EntryListParams(TypedDict, total=False): + answered_only: bool + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + sort: Literal["created_at", "answered_at"] + + unanswered_only: bool diff --git a/src/codex/types/projects/entry_list_response.py b/src/codex/types/projects/entry_list_response.py new file mode 100644 index 00000000..f415058e --- /dev/null +++ b/src/codex/types/projects/entry_list_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .entry import Entry +from ..._models import BaseModel + +__all__ = ["EntryListResponse"] + + +class EntryListResponse(BaseModel): + entries: List[Entry] + + total_count: int diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py new file mode 100644 index 00000000..b6fbc437 --- /dev/null +++ b/src/codex/types/projects/entry_query_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["EntryQueryParams"] + + +class EntryQueryParams(TypedDict, total=False): + question: Required[str] diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py new file mode 100644 index 00000000..4a7c6527 --- /dev/null +++ b/src/codex/types/projects/entry_update_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["EntryUpdateParams"] + + +class EntryUpdateParams(TypedDict, total=False): + project_id: Required[int] + + answer: Optional[str] + + question: Optional[str] diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py new file mode 100644 index 00000000..32f55fd2 --- /dev/null +++ b/tests/api_resources/projects/test_entries.py @@ -0,0 +1,661 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, Optional, cast + +import pytest + +from codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.projects import ( + Entry, + EntryListResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEntries: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_create(self, client: Codex) -> None: + entry = client.projects.entries.create( + project_id=0, + question="question", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + entry = client.projects.entries.create( + project_id=0, + question="question", + answer="answer", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.create( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.create( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + entry = client.projects.entries.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.retrieve( + entry_id="", + project_id=0, + ) + + @pytest.mark.skip() + @parametrize + def test_method_update(self, client: Codex) -> None: + entry = client.projects.entries.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_update_with_all_params(self, client: Codex) -> None: + entry = client.projects.entries.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + answer="answer", + question="question", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_update(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_update(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_update(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.update( + entry_id="", + project_id=0, + ) + + @pytest.mark.skip() + @parametrize + def test_method_list(self, client: Codex) -> None: + entry = client.projects.entries.list( + project_id=0, + ) + assert_matches_type(EntryListResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + entry = client.projects.entries.list( + project_id=0, + answered_only=True, + limit=1, + offset=0, + order="asc", + sort="created_at", + unanswered_only=True, + ) + assert_matches_type(EntryListResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.list( + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(EntryListResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.list( + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(EntryListResponse, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_delete(self, client: Codex) -> None: + entry = client.projects.entries.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert entry is None + + @pytest.mark.skip() + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert entry is None + + @pytest.mark.skip() + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert entry is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.delete( + entry_id="", + project_id=0, + ) + + @pytest.mark.skip() + @parametrize + def test_method_add_question(self, client: Codex) -> None: + entry = client.projects.entries.add_question( + project_id=0, + question="question", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_add_question(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.add_question( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_add_question(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.add_question( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_query(self, client: Codex) -> None: + entry = client.projects.entries.query( + project_id=0, + question="question", + ) + assert_matches_type(Optional[Entry], entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_query(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.query( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Optional[Entry], entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_query(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.query( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Optional[Entry], entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncEntries: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.create( + project_id=0, + question="question", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.create( + project_id=0, + question="question", + answer="answer", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.create( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.create( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.retrieve( + entry_id="", + project_id=0, + ) + + @pytest.mark.skip() + @parametrize + async def test_method_update(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + answer="answer", + question="question", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_update(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_update(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.update( + entry_id="", + project_id=0, + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.list( + project_id=0, + ) + assert_matches_type(EntryListResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.list( + project_id=0, + answered_only=True, + limit=1, + offset=0, + order="asc", + sort="created_at", + unanswered_only=True, + ) + assert_matches_type(EntryListResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.list( + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(EntryListResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.list( + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(EntryListResponse, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + assert entry is None + + @pytest.mark.skip() + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert entry is None + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert entry is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.delete( + entry_id="", + project_id=0, + ) + + @pytest.mark.skip() + @parametrize + async def test_method_add_question(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.add_question( + project_id=0, + question="question", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.add_question( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.add_question( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_query(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.query( + project_id=0, + question="question", + ) + assert_matches_type(Optional[Entry], entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_query(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.query( + project_id=0, + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Optional[Entry], entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.query( + project_id=0, + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Optional[Entry], entry, path=["response"]) + + assert cast(Any, response.is_closed) is True From c1ac8644d17591b4230981ea10ebe92abd891f33 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:19:48 +0000 Subject: [PATCH 014/320] fix: update pagination scheme (#9) --- src/codex/pagination.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/codex/pagination.py b/src/codex/pagination.py index 963a4310..4798baae 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -1,18 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast +from typing import List, Generic, TypeVar, Optional from typing_extensions import override -from httpx import Response - -from ._utils import is_mapping -from ._models import BaseModel from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage __all__ = ["SyncMyOffsetPage", "AsyncMyOffsetPage"] -_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) - _T = TypeVar("_T") @@ -37,15 +31,6 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) - @classmethod - def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 - return cls.construct( - None, - **{ - **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), - }, - ) - class AsyncMyOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): items: List[_T] @@ -67,12 +52,3 @@ def next_page_info(self) -> Optional[PageInfo]: current_count = offset + length return PageInfo(params={"offset": current_count}) - - @classmethod - def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 - return cls.construct( - None, - **{ - **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), - }, - ) From 00670d5c64a81cf4c5719cd65f9ea2b460de3f61 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:25:27 +0000 Subject: [PATCH 015/320] fix: update pagination scheme (#10) --- src/codex/pagination.py | 115 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/src/codex/pagination.py b/src/codex/pagination.py index 4798baae..b38b5f45 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -1,16 +1,27 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Generic, TypeVar, Optional +from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast from typing_extensions import override +from httpx import Response + +from ._utils import is_mapping +from ._models import BaseModel from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage -__all__ = ["SyncMyOffsetPage", "AsyncMyOffsetPage"] +__all__ = [ + "SyncMyOffsetPageTopLevelArray", + "AsyncMyOffsetPageTopLevelArray", + "SyncOffsetPageEntries", + "AsyncOffsetPageEntries", +] + +_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) _T = TypeVar("_T") -class SyncMyOffsetPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): +class SyncMyOffsetPageTopLevelArray(BaseSyncPage[_T], BasePage[_T], Generic[_T]): items: List[_T] @override @@ -31,8 +42,17 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) + @classmethod + def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 + return cls.construct( + None, + **{ + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), + }, + ) + -class AsyncMyOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): +class AsyncMyOffsetPageTopLevelArray(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): items: List[_T] @override @@ -52,3 +72,90 @@ def next_page_info(self) -> Optional[PageInfo]: current_count = offset + length return PageInfo(params={"offset": current_count}) + + @classmethod + def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 + return cls.construct( + None, + **{ + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), + }, + ) + + +class SyncOffsetPageEntries(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + entries: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + entries = self.entries + if not entries: + return [] + return entries + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + @classmethod + def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 + return cls.construct( + None, + **{ + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}), + }, + ) + + +class AsyncOffsetPageEntries(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + entries: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + entries = self.entries + if not entries: + return [] + return entries + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + @classmethod + def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 + return cls.construct( + None, + **{ + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}), + }, + ) From f95537f933a9305609d85335f638c4a29c1d20e0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:26:43 +0000 Subject: [PATCH 016/320] fix: update pagination scheme (#11) --- api.md | 4 ++-- src/codex/pagination.py | 18 --------------- src/codex/resources/projects/entries.py | 22 ++++++++++--------- src/codex/types/projects/__init__.py | 1 - .../types/projects/entry_list_response.py | 14 ------------ tests/api_resources/projects/test_entries.py | 18 +++++++-------- 6 files changed, 23 insertions(+), 54 deletions(-) delete mode 100644 src/codex/types/projects/entry_list_response.py diff --git a/api.md b/api.md index 3665b5ba..d4720cc2 100644 --- a/api.md +++ b/api.md @@ -116,7 +116,7 @@ Methods: Types: ```python -from codex.types.projects import Entry, EntryListResponse +from codex.types.projects import Entry ``` Methods: @@ -124,7 +124,7 @@ Methods: - client.projects.entries.create(project_id, \*\*params) -> Entry - client.projects.entries.retrieve(entry_id, \*, project_id) -> Entry - client.projects.entries.update(entry_id, \*, project_id, \*\*params) -> Entry -- client.projects.entries.list(project_id, \*\*params) -> EntryListResponse +- client.projects.entries.list(project_id, \*\*params) -> SyncOffsetPageEntries[Entry] - client.projects.entries.delete(entry_id, \*, project_id) -> None - client.projects.entries.add_question(project_id, \*\*params) -> Entry - client.projects.entries.query(project_id, \*\*params) -> Optional[Entry] diff --git a/src/codex/pagination.py b/src/codex/pagination.py index b38b5f45..48ed5018 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -112,15 +112,6 @@ def next_page_info(self) -> Optional[PageInfo]: return None - @classmethod - def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 - return cls.construct( - None, - **{ - **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}), - }, - ) - class AsyncOffsetPageEntries(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): entries: List[_T] @@ -150,12 +141,3 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) return None - - @classmethod - def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003 - return cls.construct( - None, - **{ - **(cast(Mapping[str, Any], data) if is_mapping(data) else {"entries": data}), - }, - ) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 6121842e..59bc9f7f 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -20,7 +20,8 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._base_client import make_request_options +from ...pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries +from ..._base_client import AsyncPaginator, make_request_options from ...types.projects import ( entry_list_params, entry_query_params, @@ -29,7 +30,6 @@ entry_add_question_params, ) from ...types.projects.entry import Entry -from ...types.projects.entry_list_response import EntryListResponse __all__ = ["EntriesResource", "AsyncEntriesResource"] @@ -187,7 +187,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> EntryListResponse: + ) -> SyncOffsetPageEntries[Entry]: """ List knowledge entries for a project. @@ -200,8 +200,9 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get( + return self._get_api_list( f"/api/projects/{project_id}/entries/", + page=SyncOffsetPageEntries[Entry], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -219,7 +220,7 @@ def list( entry_list_params.EntryListParams, ), ), - cast_to=EntryListResponse, + model=Entry, ) def delete( @@ -469,7 +470,7 @@ async def update( cast_to=Entry, ) - async def list( + def list( self, project_id: int, *, @@ -485,7 +486,7 @@ async def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> EntryListResponse: + ) -> AsyncPaginator[Entry, AsyncOffsetPageEntries[Entry]]: """ List knowledge entries for a project. @@ -498,14 +499,15 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._get( + return self._get_api_list( f"/api/projects/{project_id}/entries/", + page=AsyncOffsetPageEntries[Entry], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( + query=maybe_transform( { "answered_only": answered_only, "limit": limit, @@ -517,7 +519,7 @@ async def list( entry_list_params.EntryListParams, ), ), - cast_to=EntryListResponse, + model=Entry, ) async def delete( diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index b79e23f2..44c304f5 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -7,7 +7,6 @@ from .entry_list_params import EntryListParams as EntryListParams from .entry_query_params import EntryQueryParams as EntryQueryParams from .entry_create_params import EntryCreateParams as EntryCreateParams -from .entry_list_response import EntryListResponse as EntryListResponse from .entry_update_params import EntryUpdateParams as EntryUpdateParams from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse diff --git a/src/codex/types/projects/entry_list_response.py b/src/codex/types/projects/entry_list_response.py deleted file mode 100644 index f415058e..00000000 --- a/src/codex/types/projects/entry_list_response.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from .entry import Entry -from ..._models import BaseModel - -__all__ = ["EntryListResponse"] - - -class EntryListResponse(BaseModel): - entries: List[Entry] - - total_count: int diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 32f55fd2..e1c36e3b 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -9,9 +9,9 @@ from codex import Codex, AsyncCodex from tests.utils import assert_matches_type +from codex.pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries from codex.types.projects import ( Entry, - EntryListResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -176,7 +176,7 @@ def test_method_list(self, client: Codex) -> None: entry = client.projects.entries.list( project_id=0, ) - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -190,7 +190,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: sort="created_at", unanswered_only=True, ) - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -202,7 +202,7 @@ def test_raw_response_list(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = response.parse() - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -214,7 +214,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = response.parse() - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) assert cast(Any, response.is_closed) is True @@ -498,7 +498,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.list( project_id=0, ) - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -512,7 +512,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No sort="created_at", unanswered_only=True, ) - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -524,7 +524,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = await response.parse() - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -536,7 +536,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = await response.parse() - assert_matches_type(EntryListResponse, entry, path=["response"]) + assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) assert cast(Any, response.is_closed) is True From 2af5cb93495a2c0a3c31bee08190879d2e2760ab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:34:49 +0000 Subject: [PATCH 017/320] fix: update paginated endpoints (#12) --- README.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/README.md b/README.md index 634367e0..fd13a0ba 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,75 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## Pagination + +List methods in the Codex API are paginated. + +This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually: + +```python +from codex import Codex + +client = Codex() + +all_entries = [] +# Automatically fetches more pages as needed. +for entry in client.projects.entries.list( + project_id=0, +): + # Do something with entry here + all_entries.append(entry) +print(all_entries) +``` + +Or, asynchronously: + +```python +import asyncio +from codex import AsyncCodex + +client = AsyncCodex() + + +async def main() -> None: + all_entries = [] + # Iterate through items across all pages, issuing requests as needed. + async for entry in client.projects.entries.list( + project_id=0, + ): + all_entries.append(entry) + print(all_entries) + + +asyncio.run(main()) +``` + +Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: + +```python +first_page = await client.projects.entries.list( + project_id=0, +) +if first_page.has_next_page(): + print(f"will fetch next page using these details: {first_page.next_page_info()}") + next_page = await first_page.get_next_page() + print(f"number of items we just fetched: {len(next_page.entries)}") + +# Remove `await` for non-async usage. +``` + +Or just work directly with the returned data: + +```python +first_page = await client.projects.entries.list( + project_id=0, +) +for entry in first_page.entries: + print(entry.id) + +# Remove `await` for non-async usage. +``` + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised. From 39cd689f2fe52b141dfd660c83435b5487148f55 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:36:23 +0000 Subject: [PATCH 018/320] fix: don't publish for now (#13) --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd13a0ba..7a8ff536 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,13 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean ## Installation ```sh -# install from PyPI -pip install --pre codex-sdk +# install from the production repo +pip install git+ssh://git@github.com/cleanlab/codex-python.git ``` +> [!NOTE] +> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex-sdk` + ## Usage The full API of this library can be found in [api.md](api.md). From 2a9e03778587edf310c16d2e2d026d156a9fa55e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:38:30 +0000 Subject: [PATCH 019/320] fix: switch to MIT license (#14) --- LICENSE | 202 +------------------------------------------------ pyproject.toml | 4 +- 2 files changed, 6 insertions(+), 200 deletions(-) diff --git a/LICENSE b/LICENSE index f00d3a6a..56f06ba4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,7 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright 2025 Codex - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - 1. Definitions. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2025 Codex - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pyproject.toml b/pyproject.toml index 647b75c0..30a3e081 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "codex-sdk" version = "0.0.1-alpha.0" description = "The official Python library for the Codex API" dynamic = ["readme"] -license = "Apache-2.0" +license = "MIT" authors = [ { name = "Codex", email = "team@cleanlab.ai" }, ] @@ -30,7 +30,7 @@ classifiers = [ "Operating System :: POSIX :: Linux", "Operating System :: Microsoft :: Windows", "Topic :: Software Development :: Libraries :: Python Modules", - "License :: OSI Approved :: Apache Software License" + "License :: OSI Approved :: MIT License" ] [project.urls] From fa9fd48bbebcbfe150d64612bfb461ff25141897 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:43:08 +0000 Subject: [PATCH 020/320] fix: updload_spec = false (#15) --- .github/workflows/ci.yml | 19 ------------------- .stats.yml | 1 - 2 files changed, 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40293964..565ec95e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,24 +30,5 @@ jobs: - name: Run lints run: ./scripts/lint - test: - name: test - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - 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_INSTALL_OPTION: '--yes' - - - name: Bootstrap - run: ./scripts/bootstrap - - name: Run tests - run: ./scripts/test diff --git a/.stats.yml b/.stats.yml index 66033eb6..3a1d4dfc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1 @@ configured_endpoints: 29 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fcodex-b706b59cb40e75ffd920fc42483227383f9ef24ecb1cb1eb5f9424b388997444.yml From 2d481364b5f40a65ce4d2ace13d515927dbcf07c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:46:12 +0000 Subject: [PATCH 021/320] chore(internal): version bump (#16) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c4762802..ba6c3483 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1-alpha.0" + ".": "0.1.0-alpha.1" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 30a3e081..81892d12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.0.1-alpha.0" +version = "0.1.0-alpha.1" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index fd0542b8..96b3eab2 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.0.1-alpha.0" # x-release-please-version +__version__ = "0.1.0-alpha.1" # x-release-please-version From ff9efe547af75dda3054cea763c82287612a0982 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:16:42 +0000 Subject: [PATCH 022/320] feat(api): api update (#17) --- README.md | 8 +- api.md | 2 +- src/codex/resources/projects/access_keys.py | 89 +++-- src/codex/resources/projects/entries.py | 56 +++- src/codex/resources/projects/projects.py | 32 +- src/codex/types/organization_schema_public.py | 3 + src/codex/types/project_return_schema.py | 2 +- ...access_key_retrieve_project_id_response.py | 2 +- src/codex/types/projects/access_key_schema.py | 4 +- .../projects/access_key_update_params.py | 2 +- .../types/projects/entry_update_params.py | 2 +- .../projects/test_access_keys.py | 303 +++++++++++++----- tests/api_resources/projects/test_entries.py | 214 +++++++++---- tests/api_resources/test_projects.py | 120 +++++-- 14 files changed, 628 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index 7a8ff536..7b5fda1d 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ client = Codex() all_entries = [] # Automatically fetches more pages as needed. for entry in client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ): # Do something with entry here all_entries.append(entry) @@ -118,7 +118,7 @@ async def main() -> None: all_entries = [] # Iterate through items across all pages, issuing requests as needed. async for entry in client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ): all_entries.append(entry) print(all_entries) @@ -131,7 +131,7 @@ Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get ```python first_page = await client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) if first_page.has_next_page(): print(f"will fetch next page using these details: {first_page.next_page_info()}") @@ -145,7 +145,7 @@ Or just work directly with the returned data: ```python first_page = await client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) for entry in first_page.entries: print(entry.id) diff --git a/api.md b/api.md index d4720cc2..48af8b68 100644 --- a/api.md +++ b/api.md @@ -108,7 +108,7 @@ Methods: - client.projects.access_keys.update(access_key_id, \*, project_id, \*\*params) -> AccessKeySchema - client.projects.access_keys.list(project_id) -> AccessKeyListResponse - client.projects.access_keys.delete(access_key_id, \*, project_id) -> None -- client.projects.access_keys.retrieve_project_id() -> AccessKeyRetrieveProjectIDResponse +- client.projects.access_keys.retrieve_project_id() -> str - client.projects.access_keys.revoke(access_key_id, \*, project_id) -> None ## Entries diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index e282ad4f..8a580f49 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -24,7 +24,6 @@ from ...types.projects import access_key_create_params, access_key_update_params from ...types.projects.access_key_schema import AccessKeySchema from ...types.projects.access_key_list_response import AccessKeyListResponse -from ...types.projects.access_key_retrieve_project_id_response import AccessKeyRetrieveProjectIDResponse __all__ = ["AccessKeysResource", "AsyncAccessKeysResource"] @@ -51,7 +50,7 @@ def with_streaming_response(self) -> AccessKeysResourceWithStreamingResponse: def create( self, - project_id: int, + project_id: str, *, name: str, description: Optional[str] | NotGiven = NOT_GIVEN, @@ -75,6 +74,8 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._post( f"/api/projects/{project_id}/access_keys/", body=maybe_transform( @@ -93,9 +94,9 @@ def create( def retrieve( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_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, @@ -115,6 +116,10 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") return self._get( f"/api/projects/{project_id}/access_keys/{access_key_id}", options=make_request_options( @@ -125,9 +130,9 @@ def retrieve( def update( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_id: str, name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, @@ -150,6 +155,10 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") return self._put( f"/api/projects/{project_id}/access_keys/{access_key_id}", body=maybe_transform( @@ -168,7 +177,7 @@ def update( def list( self, - project_id: int, + project_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. @@ -189,6 +198,8 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._get( f"/api/projects/{project_id}/access_keys/", options=make_request_options( @@ -199,9 +210,9 @@ def list( def delete( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_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, @@ -221,6 +232,10 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( f"/api/projects/{project_id}/access_keys/{access_key_id}", @@ -239,21 +254,21 @@ def retrieve_project_id( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccessKeyRetrieveProjectIDResponse: + ) -> str: """Get the project ID from an access key.""" return self._get( "/api/projects/id_from_access_key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=int, + cast_to=str, ) def revoke( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_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, @@ -273,6 +288,10 @@ def revoke( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._post( f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke", @@ -305,7 +324,7 @@ def with_streaming_response(self) -> AsyncAccessKeysResourceWithStreamingRespons async def create( self, - project_id: int, + project_id: str, *, name: str, description: Optional[str] | NotGiven = NOT_GIVEN, @@ -329,6 +348,8 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._post( f"/api/projects/{project_id}/access_keys/", body=await async_maybe_transform( @@ -347,9 +368,9 @@ async def create( async def retrieve( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_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, @@ -369,6 +390,10 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") return await self._get( f"/api/projects/{project_id}/access_keys/{access_key_id}", options=make_request_options( @@ -379,9 +404,9 @@ async def retrieve( async def update( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_id: str, name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, @@ -404,6 +429,10 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") return await self._put( f"/api/projects/{project_id}/access_keys/{access_key_id}", body=await async_maybe_transform( @@ -422,7 +451,7 @@ async def update( async def list( self, - project_id: int, + project_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. @@ -443,6 +472,8 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._get( f"/api/projects/{project_id}/access_keys/", options=make_request_options( @@ -453,9 +484,9 @@ async def list( async def delete( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_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, @@ -475,6 +506,10 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( f"/api/projects/{project_id}/access_keys/{access_key_id}", @@ -493,21 +528,21 @@ async def retrieve_project_id( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccessKeyRetrieveProjectIDResponse: + ) -> str: """Get the project ID from an access key.""" return await self._get( "/api/projects/id_from_access_key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=int, + cast_to=str, ) async def revoke( self, - access_key_id: int, + access_key_id: str, *, - project_id: int, + project_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, @@ -527,6 +562,10 @@ async def revoke( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not access_key_id: + raise ValueError(f"Expected a non-empty value for `access_key_id` but received {access_key_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._post( f"/api/projects/{project_id}/access_keys/{access_key_id}/revoke", diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 59bc9f7f..56c893c0 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -56,7 +56,7 @@ def with_streaming_response(self) -> EntriesResourceWithStreamingResponse: def create( self, - project_id: int, + project_id: str, *, question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, @@ -79,6 +79,8 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._post( f"/api/projects/{project_id}/entries/", body=maybe_transform( @@ -98,7 +100,7 @@ def retrieve( self, entry_id: str, *, - project_id: int, + project_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, @@ -118,6 +120,8 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") if not entry_id: raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") return self._get( @@ -132,7 +136,7 @@ def update( self, entry_id: str, *, - project_id: int, + project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -154,6 +158,8 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") if not entry_id: raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") return self._put( @@ -173,7 +179,7 @@ def update( def list( self, - project_id: int, + project_id: str, *, answered_only: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, @@ -200,6 +206,8 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._get_api_list( f"/api/projects/{project_id}/entries/", page=SyncOffsetPageEntries[Entry], @@ -227,7 +235,7 @@ def delete( self, entry_id: str, *, - project_id: int, + project_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, @@ -247,6 +255,8 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") if not entry_id: raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} @@ -260,7 +270,7 @@ def delete( def add_question( self, - project_id: int, + project_id: str, *, question: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -285,6 +295,8 @@ def add_question( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._post( f"/api/projects/{project_id}/entries/add_question", body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), @@ -296,7 +308,7 @@ def add_question( def query( self, - project_id: int, + project_id: str, *, question: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -323,6 +335,8 @@ def query( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._post( f"/api/projects/{project_id}/entries/query", body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams), @@ -355,7 +369,7 @@ def with_streaming_response(self) -> AsyncEntriesResourceWithStreamingResponse: async def create( self, - project_id: int, + project_id: str, *, question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, @@ -378,6 +392,8 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._post( f"/api/projects/{project_id}/entries/", body=await async_maybe_transform( @@ -397,7 +413,7 @@ async def retrieve( self, entry_id: str, *, - project_id: int, + project_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, @@ -417,6 +433,8 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") if not entry_id: raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") return await self._get( @@ -431,7 +449,7 @@ async def update( self, entry_id: str, *, - project_id: int, + project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -453,6 +471,8 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") if not entry_id: raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") return await self._put( @@ -472,7 +492,7 @@ async def update( def list( self, - project_id: int, + project_id: str, *, answered_only: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, @@ -499,6 +519,8 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._get_api_list( f"/api/projects/{project_id}/entries/", page=AsyncOffsetPageEntries[Entry], @@ -526,7 +548,7 @@ async def delete( self, entry_id: str, *, - project_id: int, + project_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, @@ -546,6 +568,8 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") if not entry_id: raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} @@ -559,7 +583,7 @@ async def delete( async def add_question( self, - project_id: int, + project_id: str, *, question: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -584,6 +608,8 @@ async def add_question( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._post( f"/api/projects/{project_id}/entries/add_question", body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), @@ -595,7 +621,7 @@ async def add_question( async def query( self, - project_id: int, + project_id: str, *, question: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -622,6 +648,8 @@ async def query( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._post( f"/api/projects/{project_id}/entries/query", body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams), diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index d77e63a1..8a5f35a1 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -116,7 +116,7 @@ def create( def retrieve( self, - project_id: int, + project_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. @@ -137,6 +137,8 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._get( f"/api/projects/{project_id}", options=make_request_options( @@ -147,7 +149,7 @@ def retrieve( def update( self, - project_id: int, + project_id: str, *, config: project_update_params.Config, name: str, @@ -171,6 +173,8 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._put( f"/api/projects/{project_id}", body=maybe_transform( @@ -224,7 +228,7 @@ def list( def delete( self, - project_id: int, + project_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. @@ -245,6 +249,8 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( f"/api/projects/{project_id}", @@ -256,7 +262,7 @@ def delete( def export( self, - project_id: int, + project_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. @@ -277,6 +283,8 @@ def export( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return self._get( f"/api/projects/{project_id}/export", options=make_request_options( @@ -359,7 +367,7 @@ async def create( async def retrieve( self, - project_id: int, + project_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. @@ -380,6 +388,8 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._get( f"/api/projects/{project_id}", options=make_request_options( @@ -390,7 +400,7 @@ async def retrieve( async def update( self, - project_id: int, + project_id: str, *, config: project_update_params.Config, name: str, @@ -414,6 +424,8 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._put( f"/api/projects/{project_id}", body=await async_maybe_transform( @@ -469,7 +481,7 @@ async def list( async def delete( self, - project_id: int, + project_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. @@ -490,6 +502,8 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( f"/api/projects/{project_id}", @@ -501,7 +515,7 @@ async def delete( async def export( self, - project_id: int, + project_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. @@ -522,6 +536,8 @@ async def export( timeout: Override the client-level default timeout for this request, in seconds """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") return await self._get( f"/api/projects/{project_id}/export", options=make_request_options( diff --git a/src/codex/types/organization_schema_public.py b/src/codex/types/organization_schema_public.py index ed245c50..decc8559 100644 --- a/src/codex/types/organization_schema_public.py +++ b/src/codex/types/organization_schema_public.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel @@ -14,4 +15,6 @@ class OrganizationSchemaPublic(BaseModel): name: str + payment_status: Literal["NULL", "FIRST_OVERAGE_LENIENT", "SECOND_OVERAGE_USAGE_BLOCKED"] + updated_at: datetime diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 7f6b06a2..85312729 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -13,7 +13,7 @@ class Config(BaseModel): class ProjectReturnSchema(BaseModel): - id: int + id: str config: Config diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py index 5659cd21..b7b53137 100644 --- a/src/codex/types/projects/access_key_retrieve_project_id_response.py +++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py @@ -4,4 +4,4 @@ __all__ = ["AccessKeyRetrieveProjectIDResponse"] -AccessKeyRetrieveProjectIDResponse: TypeAlias = int +AccessKeyRetrieveProjectIDResponse: TypeAlias = str diff --git a/src/codex/types/projects/access_key_schema.py b/src/codex/types/projects/access_key_schema.py index 3e023aae..aaf5d0fe 100644 --- a/src/codex/types/projects/access_key_schema.py +++ b/src/codex/types/projects/access_key_schema.py @@ -10,7 +10,7 @@ class AccessKeySchema(BaseModel): - id: int + id: str token: str @@ -20,7 +20,7 @@ class AccessKeySchema(BaseModel): name: str - project_id: int + project_id: str status: Literal["active", "expired", "revoked"] diff --git a/src/codex/types/projects/access_key_update_params.py b/src/codex/types/projects/access_key_update_params.py index 2f40f335..f11e825f 100644 --- a/src/codex/types/projects/access_key_update_params.py +++ b/src/codex/types/projects/access_key_update_params.py @@ -12,7 +12,7 @@ class AccessKeyUpdateParams(TypedDict, total=False): - project_id: Required[int] + project_id: Required[str] name: Required[str] diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py index 4a7c6527..0a676f31 100644 --- a/src/codex/types/projects/entry_update_params.py +++ b/src/codex/types/projects/entry_update_params.py @@ -9,7 +9,7 @@ class EntryUpdateParams(TypedDict, total=False): - project_id: Required[int] + project_id: Required[str] answer: Optional[str] diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index cdb893e6..6ec8ef9b 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -13,7 +13,6 @@ from codex.types.projects import ( AccessKeySchema, AccessKeyListResponse, - AccessKeyRetrieveProjectIDResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -26,7 +25,7 @@ class TestAccessKeys: @parametrize def test_method_create(self, client: Codex) -> None: access_key = client.projects.access_keys.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @@ -35,7 +34,7 @@ def test_method_create(self, client: Codex) -> None: @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -46,7 +45,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) @@ -59,7 +58,7 @@ def test_raw_response_create(self, client: Codex) -> None: @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) as response: assert not response.is_closed @@ -70,12 +69,21 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_create(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.access_keys.with_raw_response.create( + project_id="", + name="name", + ) + @pytest.mark.skip() @parametrize def test_method_retrieve(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @@ -83,8 +91,8 @@ def test_method_retrieve(self, client: Codex) -> None: @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.retrieve( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -96,8 +104,8 @@ def test_raw_response_retrieve(self, client: Codex) -> None: @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.retrieve( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -107,12 +115,27 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.access_keys.with_raw_response.retrieve( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + client.projects.access_keys.with_raw_response.retrieve( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize def test_method_update(self, client: Codex) -> None: access_key = client.projects.access_keys.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @@ -121,8 +144,8 @@ def test_method_update(self, client: Codex) -> None: @parametrize def test_method_update_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -133,8 +156,8 @@ def test_method_update_with_all_params(self, client: Codex) -> None: @parametrize def test_raw_response_update(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) @@ -147,8 +170,8 @@ def test_raw_response_update(self, client: Codex) -> None: @parametrize def test_streaming_response_update(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) as response: assert not response.is_closed @@ -159,11 +182,28 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_update(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.access_keys.with_raw_response.update( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + name="name", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + client.projects.access_keys.with_raw_response.update( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: access_key = client.projects.access_keys.list( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @@ -171,7 +211,7 @@ def test_method_list(self, client: Codex) -> None: @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.list( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -183,7 +223,7 @@ def test_raw_response_list(self, client: Codex) -> None: @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.list( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -193,12 +233,20 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.access_keys.with_raw_response.list( + "", + ) + @pytest.mark.skip() @parametrize def test_method_delete(self, client: Codex) -> None: access_key = client.projects.access_keys.delete( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert access_key is None @@ -206,8 +254,8 @@ def test_method_delete(self, client: Codex) -> None: @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.delete( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -219,8 +267,8 @@ def test_raw_response_delete(self, client: Codex) -> None: @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.delete( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -230,11 +278,26 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.access_keys.with_raw_response.delete( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + client.projects.access_keys.with_raw_response.delete( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize def test_method_retrieve_project_id(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve_project_id() - assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + assert_matches_type(str, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -244,7 +307,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = response.parse() - assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + assert_matches_type(str, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -254,7 +317,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = response.parse() - assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + assert_matches_type(str, access_key, path=["response"]) assert cast(Any, response.is_closed) is True @@ -262,8 +325,8 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: @parametrize def test_method_revoke(self, client: Codex) -> None: access_key = client.projects.access_keys.revoke( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert access_key is None @@ -271,8 +334,8 @@ def test_method_revoke(self, client: Codex) -> None: @parametrize def test_raw_response_revoke(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.revoke( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -284,8 +347,8 @@ def test_raw_response_revoke(self, client: Codex) -> None: @parametrize def test_streaming_response_revoke(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.revoke( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -295,6 +358,21 @@ def test_streaming_response_revoke(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_revoke(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.access_keys.with_raw_response.revoke( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + client.projects.access_keys.with_raw_response.revoke( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + class TestAsyncAccessKeys: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -303,7 +381,7 @@ class TestAsyncAccessKeys: @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @@ -312,7 +390,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -323,7 +401,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) @@ -336,7 +414,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) as response: assert not response.is_closed @@ -347,12 +425,21 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_create(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.create( + project_id="", + name="name", + ) + @pytest.mark.skip() @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @@ -360,8 +447,8 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -373,8 +460,8 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -384,12 +471,27 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.retrieve( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.retrieve( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize async def test_method_update(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @@ -398,8 +500,8 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -410,8 +512,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> @parametrize async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) @@ -424,8 +526,8 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.update( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", ) as response: assert not response.is_closed @@ -436,11 +538,28 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_update(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.update( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + name="name", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.update( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.list( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) @@ -448,7 +567,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.list( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -460,7 +579,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.list( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -470,12 +589,20 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.list( + "", + ) + @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.delete( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert access_key is None @@ -483,8 +610,8 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.delete( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -496,8 +623,8 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.delete( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -507,11 +634,26 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.delete( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.delete( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve_project_id() - assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + assert_matches_type(str, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -521,7 +663,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = await response.parse() - assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + assert_matches_type(str, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -531,7 +673,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = await response.parse() - assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) + assert_matches_type(str, access_key, path=["response"]) assert cast(Any, response.is_closed) is True @@ -539,8 +681,8 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC @parametrize async def test_method_revoke(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.revoke( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert access_key is None @@ -548,8 +690,8 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.revoke( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -561,8 +703,8 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.revoke( - access_key_id=0, - project_id=0, + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -571,3 +713,18 @@ async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None assert access_key is None assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_revoke(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.revoke( + access_key_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `access_key_id` but received ''"): + await async_client.projects.access_keys.with_raw_response.revoke( + access_key_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index e1c36e3b..5b51ec12 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -24,7 +24,7 @@ class TestEntries: @parametrize def test_method_create(self, client: Codex) -> None: entry = client.projects.entries.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) assert_matches_type(Entry, entry, path=["response"]) @@ -33,7 +33,7 @@ def test_method_create(self, client: Codex) -> None: @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: entry = client.projects.entries.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", answer="answer", ) @@ -43,7 +43,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.entries.with_raw_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) @@ -56,7 +56,7 @@ def test_raw_response_create(self, client: Codex) -> None: @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.entries.with_streaming_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) as response: assert not response.is_closed @@ -67,12 +67,21 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_create(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.create( + project_id="", + question="question", + ) + @pytest.mark.skip() @parametrize def test_method_retrieve(self, client: Codex) -> None: entry = client.projects.entries.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Entry, entry, path=["response"]) @@ -81,7 +90,7 @@ def test_method_retrieve(self, client: Codex) -> None: def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.entries.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -94,7 +103,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.entries.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -107,10 +116,16 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.entries.with_raw_response.retrieve( entry_id="", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @pytest.mark.skip() @@ -118,7 +133,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: def test_method_update(self, client: Codex) -> None: entry = client.projects.entries.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Entry, entry, path=["response"]) @@ -127,7 +142,7 @@ def test_method_update(self, client: Codex) -> None: def test_method_update_with_all_params(self, client: Codex) -> None: entry = client.projects.entries.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", question="question", ) @@ -138,7 +153,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: def test_raw_response_update(self, client: Codex) -> None: response = client.projects.entries.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -151,7 +166,7 @@ def test_raw_response_update(self, client: Codex) -> None: def test_streaming_response_update(self, client: Codex) -> None: with client.projects.entries.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -164,17 +179,23 @@ def test_streaming_response_update(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_path_params_update(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.entries.with_raw_response.update( entry_id="", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: entry = client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) @@ -182,7 +203,7 @@ def test_method_list(self, client: Codex) -> None: @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: entry = client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answered_only=True, limit=1, offset=0, @@ -196,7 +217,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.entries.with_raw_response.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -208,7 +229,7 @@ def test_raw_response_list(self, client: Codex) -> None: @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.entries.with_streaming_response.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -218,12 +239,20 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.list( + project_id="", + ) + @pytest.mark.skip() @parametrize def test_method_delete(self, client: Codex) -> None: entry = client.projects.entries.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert entry is None @@ -232,7 +261,7 @@ def test_method_delete(self, client: Codex) -> None: def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.entries.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -245,7 +274,7 @@ def test_raw_response_delete(self, client: Codex) -> None: def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.entries.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -258,17 +287,23 @@ def test_streaming_response_delete(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): client.projects.entries.with_raw_response.delete( entry_id="", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @pytest.mark.skip() @parametrize def test_method_add_question(self, client: Codex) -> None: entry = client.projects.entries.add_question( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) assert_matches_type(Entry, entry, path=["response"]) @@ -277,7 +312,7 @@ def test_method_add_question(self, client: Codex) -> None: @parametrize def test_raw_response_add_question(self, client: Codex) -> None: response = client.projects.entries.with_raw_response.add_question( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) @@ -290,7 +325,7 @@ def test_raw_response_add_question(self, client: Codex) -> None: @parametrize def test_streaming_response_add_question(self, client: Codex) -> None: with client.projects.entries.with_streaming_response.add_question( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) as response: assert not response.is_closed @@ -301,11 +336,20 @@ def test_streaming_response_add_question(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_add_question(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.add_question( + project_id="", + question="question", + ) + @pytest.mark.skip() @parametrize def test_method_query(self, client: Codex) -> None: entry = client.projects.entries.query( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) assert_matches_type(Optional[Entry], entry, path=["response"]) @@ -314,7 +358,7 @@ def test_method_query(self, client: Codex) -> None: @parametrize def test_raw_response_query(self, client: Codex) -> None: response = client.projects.entries.with_raw_response.query( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) @@ -327,7 +371,7 @@ def test_raw_response_query(self, client: Codex) -> None: @parametrize def test_streaming_response_query(self, client: Codex) -> None: with client.projects.entries.with_streaming_response.query( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) as response: assert not response.is_closed @@ -338,6 +382,15 @@ def test_streaming_response_query(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_query(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.query( + project_id="", + question="question", + ) + class TestAsyncEntries: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -346,7 +399,7 @@ class TestAsyncEntries: @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) assert_matches_type(Entry, entry, path=["response"]) @@ -355,7 +408,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", answer="answer", ) @@ -365,7 +418,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.entries.with_raw_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) @@ -378,7 +431,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.entries.with_streaming_response.create( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) as response: assert not response.is_closed @@ -389,12 +442,21 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_create(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.create( + project_id="", + question="question", + ) + @pytest.mark.skip() @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Entry, entry, path=["response"]) @@ -403,7 +465,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.entries.with_raw_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -416,7 +478,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.entries.with_streaming_response.retrieve( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -429,10 +491,16 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No @pytest.mark.skip() @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.retrieve( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.entries.with_raw_response.retrieve( entry_id="", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @pytest.mark.skip() @@ -440,7 +508,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: async def test_method_update(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Entry, entry, path=["response"]) @@ -449,7 +517,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", question="question", ) @@ -460,7 +528,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.entries.with_raw_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -473,7 +541,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.entries.with_streaming_response.update( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -486,17 +554,23 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None @pytest.mark.skip() @parametrize async def test_path_params_update(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.update( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.entries.with_raw_response.update( entry_id="", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) @@ -504,7 +578,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answered_only=True, limit=1, offset=0, @@ -518,7 +592,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.entries.with_raw_response.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -530,7 +604,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.entries.with_streaming_response.list( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -540,12 +614,20 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.list( + project_id="", + ) + @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert entry is None @@ -554,7 +636,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.entries.with_raw_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -567,7 +649,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.entries.with_streaming_response.delete( entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -580,17 +662,23 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None @pytest.mark.skip() @parametrize async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.delete( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): await async_client.projects.entries.with_raw_response.delete( entry_id="", - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @pytest.mark.skip() @parametrize async def test_method_add_question(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.add_question( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) assert_matches_type(Entry, entry, path=["response"]) @@ -599,7 +687,7 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: response = await async_client.projects.entries.with_raw_response.add_question( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) @@ -612,7 +700,7 @@ async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None @parametrize async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: async with async_client.projects.entries.with_streaming_response.add_question( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) as response: assert not response.is_closed @@ -623,11 +711,20 @@ async def test_streaming_response_add_question(self, async_client: AsyncCodex) - assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_add_question(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.add_question( + project_id="", + question="question", + ) + @pytest.mark.skip() @parametrize async def test_method_query(self, async_client: AsyncCodex) -> None: entry = await async_client.projects.entries.query( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) assert_matches_type(Optional[Entry], entry, path=["response"]) @@ -636,7 +733,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_query(self, async_client: AsyncCodex) -> None: response = await async_client.projects.entries.with_raw_response.query( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) @@ -649,7 +746,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: async with async_client.projects.entries.with_streaming_response.query( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) as response: assert not response.is_closed @@ -659,3 +756,12 @@ async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: assert_matches_type(Optional[Entry], entry, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_query(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.query( + project_id="", + question="question", + ) diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 388601d2..1f1f707b 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -75,7 +75,7 @@ def test_streaming_response_create(self, client: Codex) -> None: @parametrize def test_method_retrieve(self, client: Codex) -> None: project = client.projects.retrieve( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -83,7 +83,7 @@ def test_method_retrieve(self, client: Codex) -> None: @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.with_raw_response.retrieve( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -95,7 +95,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.with_streaming_response.retrieve( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -105,11 +105,19 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip() @parametrize def test_method_update(self, client: Codex) -> None: project = client.projects.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={}, name="name", ) @@ -119,7 +127,7 @@ def test_method_update(self, client: Codex) -> None: @parametrize def test_method_update_with_all_params(self, client: Codex) -> None: project = client.projects.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={"max_distance": 0}, name="name", description="description", @@ -130,7 +138,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: @parametrize def test_raw_response_update(self, client: Codex) -> None: response = client.projects.with_raw_response.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={}, name="name", ) @@ -144,7 +152,7 @@ def test_raw_response_update(self, client: Codex) -> None: @parametrize def test_streaming_response_update(self, client: Codex) -> None: with client.projects.with_streaming_response.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={}, name="name", ) as response: @@ -156,6 +164,16 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_update(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.update( + project_id="", + config={}, + name="name", + ) + @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: @@ -194,7 +212,7 @@ def test_streaming_response_list(self, client: Codex) -> None: @parametrize def test_method_delete(self, client: Codex) -> None: project = client.projects.delete( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert project is None @@ -202,7 +220,7 @@ def test_method_delete(self, client: Codex) -> None: @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.with_raw_response.delete( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -214,7 +232,7 @@ def test_raw_response_delete(self, client: Codex) -> None: @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.with_streaming_response.delete( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -224,11 +242,19 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.delete( + "", + ) + @pytest.mark.skip() @parametrize def test_method_export(self, client: Codex) -> None: project = client.projects.export( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(object, project, path=["response"]) @@ -236,7 +262,7 @@ def test_method_export(self, client: Codex) -> None: @parametrize def test_raw_response_export(self, client: Codex) -> None: response = client.projects.with_raw_response.export( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -248,7 +274,7 @@ def test_raw_response_export(self, client: Codex) -> None: @parametrize def test_streaming_response_export(self, client: Codex) -> None: with client.projects.with_streaming_response.export( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -258,6 +284,14 @@ def test_streaming_response_export(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_path_params_export(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.export( + "", + ) + class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -317,7 +351,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: project = await async_client.projects.retrieve( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -325,7 +359,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.retrieve( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -337,7 +371,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.retrieve( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -347,11 +381,19 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip() @parametrize async def test_method_update(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={}, name="name", ) @@ -361,7 +403,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={"max_distance": 0}, name="name", description="description", @@ -372,7 +414,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> @parametrize async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={}, name="name", ) @@ -386,7 +428,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.update( - project_id=0, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={}, name="name", ) as response: @@ -398,6 +440,16 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_update(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.update( + project_id="", + config={}, + name="name", + ) + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: @@ -436,7 +488,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: project = await async_client.projects.delete( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert project is None @@ -444,7 +496,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.delete( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -456,7 +508,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.delete( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -466,11 +518,19 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.delete( + "", + ) + @pytest.mark.skip() @parametrize async def test_method_export(self, async_client: AsyncCodex) -> None: project = await async_client.projects.export( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(object, project, path=["response"]) @@ -478,7 +538,7 @@ async def test_method_export(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_export(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.export( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -490,7 +550,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_export(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.export( - 0, + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -499,3 +559,11 @@ async def test_streaming_response_export(self, async_client: AsyncCodex) -> None assert_matches_type(object, project, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_export(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.export( + "", + ) From d20c3a25adb24c479a031928ef8e05c0997e0322 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:18:25 +0000 Subject: [PATCH 023/320] chore(internal): version bump (#19) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ba6c3483..f14b480a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.1" + ".": "0.1.0-alpha.2" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 81892d12..8411c1eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 96b3eab2..0ebe32a7 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.1" # x-release-please-version +__version__ = "0.1.0-alpha.2" # x-release-please-version From b2c701a35df704f4c627adf497ef91473390e7c0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:15:53 +0000 Subject: [PATCH 024/320] chore(internal): codegen related update (#20) --- mypy.ini | 2 +- requirements-dev.lock | 4 ++-- src/codex/_response.py | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mypy.ini b/mypy.ini index 88ad64a6..92f9243b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -41,7 +41,7 @@ cache_fine_grained = True # ``` # Changing this codegen to make mypy happy would increase complexity # and would not be worth it. -disable_error_code = func-returns-value +disable_error_code = func-returns-value,overload-cannot-match # https://github.com/python/mypy/issues/12162 [mypy.overrides] diff --git a/requirements-dev.lock b/requirements-dev.lock index 47eb5e34..ef078719 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,7 +48,7 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -mypy==1.13.0 +mypy==1.14.1 mypy-extensions==1.0.0 # via mypy nest-asyncio==1.6.0 @@ -68,7 +68,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.390 +pyright==1.1.392.post0 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 diff --git a/src/codex/_response.py b/src/codex/_response.py index b63926f4..174baacd 100644 --- a/src/codex/_response.py +++ b/src/codex/_response.py @@ -210,7 +210,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 codex import BaseModel`") if ( From c42ba57e709b945fde0fc1031ef2e9bd4167e98f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:47:24 +0000 Subject: [PATCH 025/320] feat(api): api update (#22) --- src/codex/resources/projects/projects.py | 37 ++++++++++++++++++-- src/codex/types/project_list_params.py | 15 +++++++- src/codex/types/project_list_response.py | 38 ++++++++++++++++++--- src/codex/types/projects/entry.py | 4 +-- src/codex/types/users/user_schema_public.py | 2 ++ tests/api_resources/test_projects.py | 28 +++++++++++++++ 6 files changed, 114 insertions(+), 10 deletions(-) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 8a5f35a1..de366ee8 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Optional +from typing_extensions import Literal import httpx @@ -195,6 +196,12 @@ def list( self, *, organization_id: str, + include_entry_counts: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + query: Optional[str] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # 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, @@ -221,7 +228,18 @@ def list( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"organization_id": organization_id}, project_list_params.ProjectListParams), + query=maybe_transform( + { + "organization_id": organization_id, + "include_entry_counts": include_entry_counts, + "limit": limit, + "offset": offset, + "order": order, + "query": query, + "sort": sort, + }, + project_list_params.ProjectListParams, + ), ), cast_to=ProjectListResponse, ) @@ -446,6 +464,12 @@ async def list( self, *, organization_id: str, + include_entry_counts: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + query: Optional[str] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # 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, @@ -473,7 +497,16 @@ async def list( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"organization_id": organization_id}, project_list_params.ProjectListParams + { + "organization_id": organization_id, + "include_entry_counts": include_entry_counts, + "limit": limit, + "offset": offset, + "order": order, + "query": query, + "sort": sort, + }, + project_list_params.ProjectListParams, ), ), cast_to=ProjectListResponse, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index 5ca07a82..c2589598 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -2,10 +2,23 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import Optional +from typing_extensions import Literal, Required, TypedDict __all__ = ["ProjectListParams"] class ProjectListParams(TypedDict, total=False): organization_id: Required[str] + + include_entry_counts: bool + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + query: Optional[str] + + sort: Literal["created_at", "updated_at"] diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 9667cf81..84aafa98 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -1,10 +1,38 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List -from typing_extensions import TypeAlias +from typing import List, Optional +from datetime import datetime -from .project_return_schema import ProjectReturnSchema +from .._models import BaseModel -__all__ = ["ProjectListResponse"] +__all__ = ["ProjectListResponse", "Project", "ProjectConfig"] -ProjectListResponse: TypeAlias = List[ProjectReturnSchema] + +class ProjectConfig(BaseModel): + max_distance: Optional[float] = None + + +class Project(BaseModel): + id: str + + config: ProjectConfig + + created_at: datetime + + created_by_user_id: str + + name: str + + organization_id: str + + updated_at: datetime + + description: Optional[str] = None + + unanswered_entries_count: Optional[int] = None + + +class ProjectListResponse(BaseModel): + projects: List[Project] + + total_count: int diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index bfa7d278..d3e1fc5f 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -9,12 +9,12 @@ class Entry(BaseModel): - id: str - created_at: datetime question: str + id: Optional[str] = None + answer: Optional[str] = None answered_at: Optional[datetime] = None diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py index 9becf37d..6d35550b 100644 --- a/src/codex/types/users/user_schema_public.py +++ b/src/codex/types/users/user_schema_public.py @@ -15,3 +15,5 @@ class UserSchemaPublic(BaseModel): email: str name: Optional[str] = None + + email_verified: Optional[bool] = None diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 1f1f707b..10d23461 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -182,6 +182,20 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + project = client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_entry_counts=True, + limit=0, + offset=0, + order="asc", + query="query", + sort="created_at", + ) + assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: @@ -458,6 +472,20 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_entry_counts=True, + limit=0, + offset=0, + order="asc", + query="query", + sort="created_at", + ) + assert_matches_type(ProjectListResponse, project, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: From 0fcb67db1b40338842bf2e9767a831df210185b3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:53:08 +0000 Subject: [PATCH 026/320] chore(internal): version bump (#23) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f14b480a..aaf968a1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.2" + ".": "0.1.0-alpha.3" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8411c1eb..e3806276 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 0ebe32a7..4d9fa623 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.2" # x-release-please-version +__version__ = "0.1.0-alpha.3" # x-release-please-version From be9461a864530f2ec435037e02917ed988ce9ad7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:17:53 +0000 Subject: [PATCH 027/320] docs(raw responses): fix duplicate `the` (#24) --- src/codex/resources/health.py | 4 ++-- src/codex/resources/organizations/billing.py | 4 ++-- src/codex/resources/organizations/organizations.py | 4 ++-- src/codex/resources/projects/access_keys.py | 4 ++-- src/codex/resources/projects/entries.py | 4 ++-- src/codex/resources/projects/projects.py | 4 ++-- src/codex/resources/users/myself/api_key.py | 4 ++-- src/codex/resources/users/myself/myself.py | 4 ++-- src/codex/resources/users/myself/organizations.py | 4 ++-- src/codex/resources/users/users.py | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py index b50884e0..6a777c63 100644 --- a/src/codex/resources/health.py +++ b/src/codex/resources/health.py @@ -23,7 +23,7 @@ class HealthResource(SyncAPIResource): @cached_property def with_raw_response(self) -> HealthResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -101,7 +101,7 @@ class AsyncHealthResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncHealthResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing.py index 9c916d83..57424963 100644 --- a/src/codex/resources/organizations/billing.py +++ b/src/codex/resources/organizations/billing.py @@ -24,7 +24,7 @@ class BillingResource(SyncAPIResource): @cached_property def with_raw_response(self) -> BillingResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -111,7 +111,7 @@ class AsyncBillingResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncBillingResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py index e91099e3..73c2542a 100644 --- a/src/codex/resources/organizations/organizations.py +++ b/src/codex/resources/organizations/organizations.py @@ -35,7 +35,7 @@ def billing(self) -> BillingResource: @cached_property def with_raw_response(self) -> OrganizationsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -93,7 +93,7 @@ def billing(self) -> AsyncBillingResource: @cached_property def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 8a580f49..bb0dc2ff 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -32,7 +32,7 @@ class AccessKeysResource(SyncAPIResource): @cached_property def with_raw_response(self) -> AccessKeysResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -306,7 +306,7 @@ class AsyncAccessKeysResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncAccessKeysResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 56c893c0..d307d3d0 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -38,7 +38,7 @@ class EntriesResource(SyncAPIResource): @cached_property def with_raw_response(self) -> EntriesResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -351,7 +351,7 @@ class AsyncEntriesResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncEntriesResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index de366ee8..38d7b036 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -56,7 +56,7 @@ def entries(self) -> EntriesResource: @cached_property def with_raw_response(self) -> ProjectsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -324,7 +324,7 @@ def entries(self) -> AsyncEntriesResource: @cached_property def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py index c2784b81..d39b124a 100644 --- a/src/codex/resources/users/myself/api_key.py +++ b/src/codex/resources/users/myself/api_key.py @@ -23,7 +23,7 @@ class APIKeyResource(SyncAPIResource): @cached_property def with_raw_response(self) -> APIKeyResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -63,7 +63,7 @@ class AsyncAPIKeyResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncAPIKeyResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py index ba4548e5..3ee27229 100644 --- a/src/codex/resources/users/myself/myself.py +++ b/src/codex/resources/users/myself/myself.py @@ -47,7 +47,7 @@ def organizations(self) -> OrganizationsResource: @cached_property def with_raw_response(self) -> MyselfResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -95,7 +95,7 @@ def organizations(self) -> AsyncOrganizationsResource: @cached_property def with_raw_response(self) -> AsyncMyselfResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/users/myself/organizations.py b/src/codex/resources/users/myself/organizations.py index 12e799aa..2d5b7127 100644 --- a/src/codex/resources/users/myself/organizations.py +++ b/src/codex/resources/users/myself/organizations.py @@ -23,7 +23,7 @@ class OrganizationsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> OrganizationsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -63,7 +63,7 @@ class AsyncOrganizationsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncOrganizationsResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index ea5019c2..8a73b474 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -24,7 +24,7 @@ def myself(self) -> MyselfResource: @cached_property def with_raw_response(self) -> UsersResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers @@ -49,7 +49,7 @@ def myself(self) -> AsyncMyselfResource: @cached_property def with_raw_response(self) -> AsyncUsersResourceWithRawResponse: """ - This property can be used as a prefix for any HTTP method call to return the + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers From 6ca3c6e0f2b59d9caa2b56d72ca3ad0f89db84be Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:18:31 +0000 Subject: [PATCH 028/320] fix(tests): make test_get_platform less flaky (#26) --- tests/test_client.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 4e76d238..d000ab1c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,6 +6,7 @@ import os import sys import json +import time import asyncio import inspect import subprocess @@ -1565,10 +1566,20 @@ async def test_main() -> None: [sys.executable, "-c", test_code], text=True, ) as process: - try: - process.wait(2) - if process.returncode: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - except subprocess.TimeoutExpired as e: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e + timeout = 10 # seconds + + start_time = time.monotonic() + while True: + return_code = process.poll() + if return_code is not None: + if return_code != 0: + raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") + + # success + break + + if time.monotonic() - start_time > timeout: + process.kill() + raise AssertionError("calling get_platform using asyncify resulted in a hung process") + + time.sleep(0.1) From 20a2d81944dbe2068f9950ce8d30dc17844bcc5a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:19:07 +0000 Subject: [PATCH 029/320] chore(internal): avoid pytest-asyncio deprecation warning (#27) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index e3806276..5432262a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,6 +129,7 @@ testpaths = ["tests"] addopts = "--tb=short" xfail_strict = true asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" filterwarnings = [ "error" ] From 2c7d26367c0fbae4a220192e1c89dbf97f8e1bf5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:15:44 +0000 Subject: [PATCH 030/320] feat(api): update api base url (#28) --- src/codex/_client.py | 4 ++-- tests/test_client.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index d9e44320..884d2f3d 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -50,8 +50,8 @@ ] ENVIRONMENTS: Dict[str, str] = { - "production": "https://api-alpha-o3gxj3oajfu.cleanlab.ai", - "staging": "https://api-alpha-staging-o3gxj3oajfu.cleanlab.ai", + "production": "https://api-codex-o3gxj3oajfu.cleanlab.ai", + "staging": "https://api-codex-staging-o3gxj3oajfu.cleanlab.ai", "local": "http://localhost:8080", } diff --git a/tests/test_client.py b/tests/test_client.py index d000ab1c..3eaa6536 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -522,7 +522,7 @@ def test_base_url_env(self) -> None: Codex(_strict_response_validation=True, environment="production") client = Codex(base_url=None, _strict_response_validation=True, environment="production") - assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") + assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", @@ -1263,7 +1263,7 @@ def test_base_url_env(self) -> None: AsyncCodex(_strict_response_validation=True, environment="production") client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production") - assert str(client.base_url).startswith("https://api-alpha-o3gxj3oajfu.cleanlab.ai") + assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai") @pytest.mark.parametrize( "client", From b4cb80f3c77dec784ab7bc1d07c5a0a792e9b640 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:54:27 +0000 Subject: [PATCH 031/320] chore(internal): version bump (#29) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aaf968a1..b56c3d0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.3" + ".": "0.1.0-alpha.4" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 5432262a..a4e19e5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.3" +version = "0.1.0-alpha.4" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 4d9fa623..751ac5dd 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.3" # x-release-please-version +__version__ = "0.1.0-alpha.4" # x-release-please-version From da6fd17de98a6b7d6bfaf351897af5650b730e72 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 01:25:08 +0000 Subject: [PATCH 032/320] feat(api): api update (#30) --- src/codex/types/users/user_schema.py | 16 +++++++++++++++- src/codex/types/users/user_schema_public.py | 10 +++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/codex/types/users/user_schema.py b/src/codex/types/users/user_schema.py index ec6ad541..b1665f21 100644 --- a/src/codex/types/users/user_schema.py +++ b/src/codex/types/users/user_schema.py @@ -11,14 +11,28 @@ class UserSchema(BaseModel): id: str + account_activated_at: Optional[datetime] = None + api_key: str api_key_timestamp: datetime created_at: datetime + discovery_source: Optional[str] = None + email: str + updated_at: datetime + + user_provided_company_name: Optional[str] = None + + first_name: Optional[str] = None + + is_account_activated: Optional[bool] = None + + last_name: Optional[str] = None + name: Optional[str] = None - updated_at: datetime + phone_number: Optional[str] = None diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py index 6d35550b..181113b0 100644 --- a/src/codex/types/users/user_schema_public.py +++ b/src/codex/types/users/user_schema_public.py @@ -14,6 +14,14 @@ class UserSchemaPublic(BaseModel): email: str + email_verified: Optional[bool] = None + + first_name: Optional[str] = None + + is_account_activated: Optional[bool] = None + + last_name: Optional[str] = None + name: Optional[str] = None - email_verified: Optional[bool] = None + phone_number: Optional[str] = None From 31b206bb301bd5c844f3c5affafc24742b204a8b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 01:27:40 +0000 Subject: [PATCH 033/320] feat(api): update prod api url (#32) --- src/codex/_client.py | 2 +- tests/test_client.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index 884d2f3d..74f0c871 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -50,7 +50,7 @@ ] ENVIRONMENTS: Dict[str, str] = { - "production": "https://api-codex-o3gxj3oajfu.cleanlab.ai", + "production": "https://api-codex.cleanlab.ai", "staging": "https://api-codex-staging-o3gxj3oajfu.cleanlab.ai", "local": "http://localhost:8080", } diff --git a/tests/test_client.py b/tests/test_client.py index 3eaa6536..b421541f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -522,7 +522,7 @@ def test_base_url_env(self) -> None: Codex(_strict_response_validation=True, environment="production") client = Codex(base_url=None, _strict_response_validation=True, environment="production") - assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai") + assert str(client.base_url).startswith("https://api-codex.cleanlab.ai") @pytest.mark.parametrize( "client", @@ -1263,7 +1263,7 @@ def test_base_url_env(self) -> None: AsyncCodex(_strict_response_validation=True, environment="production") client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production") - assert str(client.base_url).startswith("https://api-codex-o3gxj3oajfu.cleanlab.ai") + assert str(client.base_url).startswith("https://api-codex.cleanlab.ai") @pytest.mark.parametrize( "client", From 2327c11e83028eaca6acc0582a88199962a4524a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 01:35:54 +0000 Subject: [PATCH 034/320] feat(api): api update (#33) --- api.md | 2 +- src/codex/resources/projects/access_keys.py | 9 +++++---- .../access_key_retrieve_project_id_response.py | 7 +++++-- tests/api_resources/projects/test_access_keys.py | 13 +++++++------ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/api.md b/api.md index 48af8b68..d4720cc2 100644 --- a/api.md +++ b/api.md @@ -108,7 +108,7 @@ Methods: - client.projects.access_keys.update(access_key_id, \*, project_id, \*\*params) -> AccessKeySchema - client.projects.access_keys.list(project_id) -> AccessKeyListResponse - client.projects.access_keys.delete(access_key_id, \*, project_id) -> None -- client.projects.access_keys.retrieve_project_id() -> str +- client.projects.access_keys.retrieve_project_id() -> AccessKeyRetrieveProjectIDResponse - client.projects.access_keys.revoke(access_key_id, \*, project_id) -> None ## Entries diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index bb0dc2ff..d375dbca 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -24,6 +24,7 @@ from ...types.projects import access_key_create_params, access_key_update_params from ...types.projects.access_key_schema import AccessKeySchema from ...types.projects.access_key_list_response import AccessKeyListResponse +from ...types.projects.access_key_retrieve_project_id_response import AccessKeyRetrieveProjectIDResponse __all__ = ["AccessKeysResource", "AsyncAccessKeysResource"] @@ -254,14 +255,14 @@ def retrieve_project_id( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> str: + ) -> AccessKeyRetrieveProjectIDResponse: """Get the project ID from an access key.""" return self._get( "/api/projects/id_from_access_key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=str, + cast_to=AccessKeyRetrieveProjectIDResponse, ) def revoke( @@ -528,14 +529,14 @@ async def retrieve_project_id( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> str: + ) -> AccessKeyRetrieveProjectIDResponse: """Get the project ID from an access key.""" return await self._get( "/api/projects/id_from_access_key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=str, + cast_to=AccessKeyRetrieveProjectIDResponse, ) async def revoke( diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py index b7b53137..22775083 100644 --- a/src/codex/types/projects/access_key_retrieve_project_id_response.py +++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py @@ -1,7 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import TypeAlias + +from ..._models import BaseModel __all__ = ["AccessKeyRetrieveProjectIDResponse"] -AccessKeyRetrieveProjectIDResponse: TypeAlias = str + +class AccessKeyRetrieveProjectIDResponse(BaseModel): + project_id: str diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index 6ec8ef9b..240c31e1 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -13,6 +13,7 @@ from codex.types.projects import ( AccessKeySchema, AccessKeyListResponse, + AccessKeyRetrieveProjectIDResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -297,7 +298,7 @@ def test_path_params_delete(self, client: Codex) -> None: @parametrize def test_method_retrieve_project_id(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve_project_id() - assert_matches_type(str, access_key, path=["response"]) + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -307,7 +308,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = response.parse() - assert_matches_type(str, access_key, path=["response"]) + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -317,7 +318,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = response.parse() - assert_matches_type(str, access_key, path=["response"]) + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) assert cast(Any, response.is_closed) is True @@ -653,7 +654,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve_project_id() - assert_matches_type(str, access_key, path=["response"]) + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -663,7 +664,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = await response.parse() - assert_matches_type(str, access_key, path=["response"]) + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -673,7 +674,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_key = await response.parse() - assert_matches_type(str, access_key, path=["response"]) + assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) assert cast(Any, response.is_closed) is True From 65402e100d0af03996a1b5b8316f7b25de624d44 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 01:38:06 +0000 Subject: [PATCH 035/320] chore(internal): version bump (#34) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b56c3d0b..e8285b71 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.4" + ".": "0.1.0-alpha.5" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a4e19e5d..3c92ef48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.4" +version = "0.1.0-alpha.5" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 751ac5dd..134b2cab 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.4" # x-release-please-version +__version__ = "0.1.0-alpha.5" # x-release-please-version From 5168c56d2221cde28945e6b55da5483b3a028a33 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:17:53 +0000 Subject: [PATCH 036/320] chore(internal): minor style changes (#35) --- src/codex/_response.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codex/_response.py b/src/codex/_response.py index 174baacd..6e7c1cdf 100644 --- a/src/codex/_response.py +++ b/src/codex/_response.py @@ -136,6 +136,8 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: 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: if not is_stream_class_type(to): @@ -195,8 +197,6 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == bool: return cast(R, response.text.lower() == "true") - origin = get_origin(cast_to) or cast_to - if origin == APIResponse: raise RuntimeError("Unexpected state - cast_to is `APIResponse`") From 3c7a2493ba505d3faed7473df43a121abb933735 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 03:29:38 +0000 Subject: [PATCH 037/320] chore(internal): minor formatting changes (#37) --- .github/workflows/ci.yml | 3 --- scripts/bootstrap | 2 +- scripts/lint | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 565ec95e..e503784c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,6 @@ jobs: lint: name: lint runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 @@ -30,5 +29,3 @@ jobs: - name: Run lints run: ./scripts/lint - - diff --git a/scripts/bootstrap b/scripts/bootstrap index 8c5c60eb..e84fe62c 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then brew bundle check >/dev/null 2>&1 || { echo "==> Installing Homebrew dependencies…" brew bundle diff --git a/scripts/lint b/scripts/lint index 6f2bb0d0..adfc6acf 100755 --- a/scripts/lint +++ b/scripts/lint @@ -9,4 +9,3 @@ rye run lint echo "==> Making sure it imports" rye run python -c 'import codex' - From b2ec929247854ae239eebebf1055d9442f13cdd1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 18:44:47 +0000 Subject: [PATCH 038/320] feat(api): manual updates (#38) --- README.md | 2 +- src/codex/_client.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7b5fda1d..56bc216a 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ print(project_return_schema.id) While you can provide a `bearer_token` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `BEARER_TOKEN="My Bearer Token"` to your `.env` file +to add `CODEX_BEARER_TOKEN="My Bearer Token"` to your `.env` file so that your Bearer Token is not stored in source control. ## Async usage diff --git a/src/codex/_client.py b/src/codex/_client.py index 74f0c871..fc0c27bf 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -100,20 +100,20 @@ def __init__( """Construct a new synchronous Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `bearer_token` from `BEARER_TOKEN` - - `api_key` from `AUTHENTICATED_API_KEY` - - `access_key` from `PUBLIC_ACCESS_KEY` + - `bearer_token` from `CODEX_BEARER_TOKEN` + - `api_key` from `CODEX_API_KEY` + - `access_key` from `CODEX_ACCESS_KEY` """ if bearer_token is None: - bearer_token = os.environ.get("BEARER_TOKEN") + bearer_token = os.environ.get("CODEX_BEARER_TOKEN") self.bearer_token = bearer_token if api_key is None: - api_key = os.environ.get("AUTHENTICATED_API_KEY") + api_key = os.environ.get("CODEX_API_KEY") self.api_key = api_key if access_key is None: - access_key = os.environ.get("PUBLIC_ACCESS_KEY") + access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment @@ -362,20 +362,20 @@ def __init__( """Construct a new async Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `bearer_token` from `BEARER_TOKEN` - - `api_key` from `AUTHENTICATED_API_KEY` - - `access_key` from `PUBLIC_ACCESS_KEY` + - `bearer_token` from `CODEX_BEARER_TOKEN` + - `api_key` from `CODEX_API_KEY` + - `access_key` from `CODEX_ACCESS_KEY` """ if bearer_token is None: - bearer_token = os.environ.get("BEARER_TOKEN") + bearer_token = os.environ.get("CODEX_BEARER_TOKEN") self.bearer_token = bearer_token if api_key is None: - api_key = os.environ.get("AUTHENTICATED_API_KEY") + api_key = os.environ.get("CODEX_API_KEY") self.api_key = api_key if access_key is None: - access_key = os.environ.get("PUBLIC_ACCESS_KEY") + access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment From 0d6f590e6d3ba7ac5256fbabd8ca372f0756d043 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:35:19 +0000 Subject: [PATCH 039/320] chore(internal): version bump (#39) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e8285b71..4f9005ea 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.5" + ".": "0.1.0-alpha.6" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3c92ef48..0310d2c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.5" +version = "0.1.0-alpha.6" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 134b2cab..559cb92d 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.5" # x-release-please-version +__version__ = "0.1.0-alpha.6" # x-release-please-version From 5a94c5f64dfa44a7151e74483be76997385e6d60 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:57:11 +0000 Subject: [PATCH 040/320] feat(api): publish to PyPI (#40) --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 56bc216a..42ef0af5 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,10 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean ## Installation ```sh -# install from the production repo -pip install git+ssh://git@github.com/cleanlab/codex-python.git +# install from PyPI +pip install --pre codex-sdk ``` -> [!NOTE] -> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex-sdk` - ## Usage The full API of this library can be found in [api.md](api.md). From a807e3a30547b1059b87afcf5c83d1a475f09047 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:59:01 +0000 Subject: [PATCH 041/320] chore(internal): version bump (#42) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4f9005ea..b5db7ce1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.6" + ".": "0.1.0-alpha.7" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0310d2c1..6aa0de59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.6" +version = "0.1.0-alpha.7" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 559cb92d..4fb336bd 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.6" # x-release-please-version +__version__ = "0.1.0-alpha.7" # x-release-please-version From 47f0f2d82f5f204a9607adff304b947c76a87ee2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 00:47:23 +0000 Subject: [PATCH 042/320] feat(api): add new endpoints (#43) --- .stats.yml | 2 +- api.md | 53 +++++- .../organizations/billing/__init__.py | 61 +++++++ .../organizations/{ => billing}/billing.py | 110 +++++++++++- .../organizations/billing/card_details.py | 165 +++++++++++++++++ .../organizations/billing/plan_details.py | 167 ++++++++++++++++++ .../organizations/billing/setup_intent.py | 163 +++++++++++++++++ .../resources/organizations/organizations.py | 16 +- src/codex/resources/users/myself/api_key.py | 51 ++++++ src/codex/resources/users/myself/myself.py | 10 +- src/codex/resources/users/users.py | 133 ++++++++++++++ src/codex/types/__init__.py | 2 + .../types/organizations/billing/__init__.py | 7 + .../organization_billing_card_details.py | 39 ++++ .../organization_billing_plan_details.py | 30 ++++ .../organization_billing_setup_intent.py | 12 ++ .../{users/user_schema_public.py => user.py} | 6 +- .../types/user_activate_account_params.py | 27 +++ src/codex/types/users/__init__.py | 1 - .../organizations/billing/__init__.py | 1 + .../billing/test_card_details.py | 106 +++++++++++ .../billing/test_plan_details.py | 106 +++++++++++ .../billing/test_setup_intent.py | 106 +++++++++++ tests/api_resources/test_users.py | 125 +++++++++++++ .../users/myself/test_api_key.py | 57 ++++++ tests/api_resources/users/test_myself.py | 14 +- 26 files changed, 1535 insertions(+), 35 deletions(-) create mode 100644 src/codex/resources/organizations/billing/__init__.py rename src/codex/resources/organizations/{ => billing}/billing.py (68%) create mode 100644 src/codex/resources/organizations/billing/card_details.py create mode 100644 src/codex/resources/organizations/billing/plan_details.py create mode 100644 src/codex/resources/organizations/billing/setup_intent.py create mode 100644 src/codex/types/organizations/billing/__init__.py create mode 100644 src/codex/types/organizations/billing/organization_billing_card_details.py create mode 100644 src/codex/types/organizations/billing/organization_billing_plan_details.py create mode 100644 src/codex/types/organizations/billing/organization_billing_setup_intent.py rename src/codex/types/{users/user_schema_public.py => user.py} (80%) create mode 100644 src/codex/types/user_activate_account_params.py create mode 100644 tests/api_resources/organizations/billing/__init__.py create mode 100644 tests/api_resources/organizations/billing/test_card_details.py create mode 100644 tests/api_resources/organizations/billing/test_plan_details.py create mode 100644 tests/api_resources/organizations/billing/test_setup_intent.py create mode 100644 tests/api_resources/test_users.py diff --git a/.stats.yml b/.stats.yml index 3a1d4dfc..966331a1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 29 +configured_endpoints: 34 diff --git a/api.md b/api.md index d4720cc2..b2fc2f00 100644 --- a/api.md +++ b/api.md @@ -37,11 +37,57 @@ from codex.types.organizations import ( Methods: -- client.organizations.billing.invoices(organization_id) -> OrganizationBillingInvoicesSchema -- client.organizations.billing.usage(organization_id) -> OrganizationBillingUsageSchema +- client.organizations.billing.invoices(organization_id) -> OrganizationBillingInvoicesSchema +- client.organizations.billing.usage(organization_id) -> OrganizationBillingUsageSchema + +### CardDetails + +Types: + +```python +from codex.types.organizations.billing import OrganizationBillingCardDetails +``` + +Methods: + +- client.organizations.billing.card_details.retrieve(organization_id) -> Optional[OrganizationBillingCardDetails] + +### SetupIntent + +Types: + +```python +from codex.types.organizations.billing import OrganizationBillingSetupIntent +``` + +Methods: + +- client.organizations.billing.setup_intent.create(organization_id) -> OrganizationBillingSetupIntent + +### PlanDetails + +Types: + +```python +from codex.types.organizations.billing import OrganizationBillingPlanDetails +``` + +Methods: + +- client.organizations.billing.plan_details.retrieve(organization_id) -> OrganizationBillingPlanDetails # Users +Types: + +```python +from codex.types import User +``` + +Methods: + +- client.users.activate_account(\*\*params) -> User + ## Myself Types: @@ -52,12 +98,13 @@ from codex.types.users import UserSchema, UserSchemaPublic Methods: -- client.users.myself.retrieve() -> UserSchemaPublic +- client.users.myself.retrieve() -> User ### APIKey Methods: +- client.users.myself.api_key.retrieve() -> User - client.users.myself.api_key.refresh() -> UserSchema ### Organizations diff --git a/src/codex/resources/organizations/billing/__init__.py b/src/codex/resources/organizations/billing/__init__.py new file mode 100644 index 00000000..c64f6378 --- /dev/null +++ b/src/codex/resources/organizations/billing/__init__.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .billing import ( + BillingResource, + AsyncBillingResource, + BillingResourceWithRawResponse, + AsyncBillingResourceWithRawResponse, + BillingResourceWithStreamingResponse, + AsyncBillingResourceWithStreamingResponse, +) +from .card_details import ( + CardDetailsResource, + AsyncCardDetailsResource, + CardDetailsResourceWithRawResponse, + AsyncCardDetailsResourceWithRawResponse, + CardDetailsResourceWithStreamingResponse, + AsyncCardDetailsResourceWithStreamingResponse, +) +from .plan_details import ( + PlanDetailsResource, + AsyncPlanDetailsResource, + PlanDetailsResourceWithRawResponse, + AsyncPlanDetailsResourceWithRawResponse, + PlanDetailsResourceWithStreamingResponse, + AsyncPlanDetailsResourceWithStreamingResponse, +) +from .setup_intent import ( + SetupIntentResource, + AsyncSetupIntentResource, + SetupIntentResourceWithRawResponse, + AsyncSetupIntentResourceWithRawResponse, + SetupIntentResourceWithStreamingResponse, + AsyncSetupIntentResourceWithStreamingResponse, +) + +__all__ = [ + "CardDetailsResource", + "AsyncCardDetailsResource", + "CardDetailsResourceWithRawResponse", + "AsyncCardDetailsResourceWithRawResponse", + "CardDetailsResourceWithStreamingResponse", + "AsyncCardDetailsResourceWithStreamingResponse", + "SetupIntentResource", + "AsyncSetupIntentResource", + "SetupIntentResourceWithRawResponse", + "AsyncSetupIntentResourceWithRawResponse", + "SetupIntentResourceWithStreamingResponse", + "AsyncSetupIntentResourceWithStreamingResponse", + "PlanDetailsResource", + "AsyncPlanDetailsResource", + "PlanDetailsResourceWithRawResponse", + "AsyncPlanDetailsResourceWithRawResponse", + "PlanDetailsResourceWithStreamingResponse", + "AsyncPlanDetailsResourceWithStreamingResponse", + "BillingResource", + "AsyncBillingResource", + "BillingResourceWithRawResponse", + "AsyncBillingResourceWithRawResponse", + "BillingResourceWithStreamingResponse", + "AsyncBillingResourceWithStreamingResponse", +] diff --git a/src/codex/resources/organizations/billing.py b/src/codex/resources/organizations/billing/billing.py similarity index 68% rename from src/codex/resources/organizations/billing.py rename to src/codex/resources/organizations/billing/billing.py index 57424963..79c2a5c9 100644 --- a/src/codex/resources/organizations/billing.py +++ b/src/codex/resources/organizations/billing/billing.py @@ -4,23 +4,59 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.organizations.organization_billing_usage_schema import OrganizationBillingUsageSchema -from ...types.organizations.organization_billing_invoices_schema import OrganizationBillingInvoicesSchema +from .card_details import ( + CardDetailsResource, + AsyncCardDetailsResource, + CardDetailsResourceWithRawResponse, + AsyncCardDetailsResourceWithRawResponse, + CardDetailsResourceWithStreamingResponse, + AsyncCardDetailsResourceWithStreamingResponse, +) +from .plan_details import ( + PlanDetailsResource, + AsyncPlanDetailsResource, + PlanDetailsResourceWithRawResponse, + AsyncPlanDetailsResourceWithRawResponse, + PlanDetailsResourceWithStreamingResponse, + AsyncPlanDetailsResourceWithStreamingResponse, +) +from .setup_intent import ( + SetupIntentResource, + AsyncSetupIntentResource, + SetupIntentResourceWithRawResponse, + AsyncSetupIntentResourceWithRawResponse, + SetupIntentResourceWithStreamingResponse, + AsyncSetupIntentResourceWithStreamingResponse, +) +from ...._base_client import make_request_options +from ....types.organizations.organization_billing_usage_schema import OrganizationBillingUsageSchema +from ....types.organizations.organization_billing_invoices_schema import OrganizationBillingInvoicesSchema __all__ = ["BillingResource", "AsyncBillingResource"] class BillingResource(SyncAPIResource): + @cached_property + def card_details(self) -> CardDetailsResource: + return CardDetailsResource(self._client) + + @cached_property + def setup_intent(self) -> SetupIntentResource: + return SetupIntentResource(self._client) + + @cached_property + def plan_details(self) -> PlanDetailsResource: + return PlanDetailsResource(self._client) + @cached_property def with_raw_response(self) -> BillingResourceWithRawResponse: """ @@ -108,6 +144,18 @@ def usage( class AsyncBillingResource(AsyncAPIResource): + @cached_property + def card_details(self) -> AsyncCardDetailsResource: + return AsyncCardDetailsResource(self._client) + + @cached_property + def setup_intent(self) -> AsyncSetupIntentResource: + return AsyncSetupIntentResource(self._client) + + @cached_property + def plan_details(self) -> AsyncPlanDetailsResource: + return AsyncPlanDetailsResource(self._client) + @cached_property def with_raw_response(self) -> AsyncBillingResourceWithRawResponse: """ @@ -205,6 +253,18 @@ def __init__(self, billing: BillingResource) -> None: billing.usage, ) + @cached_property + def card_details(self) -> CardDetailsResourceWithRawResponse: + return CardDetailsResourceWithRawResponse(self._billing.card_details) + + @cached_property + def setup_intent(self) -> SetupIntentResourceWithRawResponse: + return SetupIntentResourceWithRawResponse(self._billing.setup_intent) + + @cached_property + def plan_details(self) -> PlanDetailsResourceWithRawResponse: + return PlanDetailsResourceWithRawResponse(self._billing.plan_details) + class AsyncBillingResourceWithRawResponse: def __init__(self, billing: AsyncBillingResource) -> None: @@ -217,6 +277,18 @@ def __init__(self, billing: AsyncBillingResource) -> None: billing.usage, ) + @cached_property + def card_details(self) -> AsyncCardDetailsResourceWithRawResponse: + return AsyncCardDetailsResourceWithRawResponse(self._billing.card_details) + + @cached_property + def setup_intent(self) -> AsyncSetupIntentResourceWithRawResponse: + return AsyncSetupIntentResourceWithRawResponse(self._billing.setup_intent) + + @cached_property + def plan_details(self) -> AsyncPlanDetailsResourceWithRawResponse: + return AsyncPlanDetailsResourceWithRawResponse(self._billing.plan_details) + class BillingResourceWithStreamingResponse: def __init__(self, billing: BillingResource) -> None: @@ -229,6 +301,18 @@ def __init__(self, billing: BillingResource) -> None: billing.usage, ) + @cached_property + def card_details(self) -> CardDetailsResourceWithStreamingResponse: + return CardDetailsResourceWithStreamingResponse(self._billing.card_details) + + @cached_property + def setup_intent(self) -> SetupIntentResourceWithStreamingResponse: + return SetupIntentResourceWithStreamingResponse(self._billing.setup_intent) + + @cached_property + def plan_details(self) -> PlanDetailsResourceWithStreamingResponse: + return PlanDetailsResourceWithStreamingResponse(self._billing.plan_details) + class AsyncBillingResourceWithStreamingResponse: def __init__(self, billing: AsyncBillingResource) -> None: @@ -240,3 +324,15 @@ def __init__(self, billing: AsyncBillingResource) -> None: self.usage = async_to_streamed_response_wrapper( billing.usage, ) + + @cached_property + def card_details(self) -> AsyncCardDetailsResourceWithStreamingResponse: + return AsyncCardDetailsResourceWithStreamingResponse(self._billing.card_details) + + @cached_property + def setup_intent(self) -> AsyncSetupIntentResourceWithStreamingResponse: + return AsyncSetupIntentResourceWithStreamingResponse(self._billing.setup_intent) + + @cached_property + def plan_details(self) -> AsyncPlanDetailsResourceWithStreamingResponse: + return AsyncPlanDetailsResourceWithStreamingResponse(self._billing.plan_details) diff --git a/src/codex/resources/organizations/billing/card_details.py b/src/codex/resources/organizations/billing/card_details.py new file mode 100644 index 00000000..94cb8a70 --- /dev/null +++ b/src/codex/resources/organizations/billing/card_details.py @@ -0,0 +1,165 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.organizations.billing.organization_billing_card_details import OrganizationBillingCardDetails + +__all__ = ["CardDetailsResource", "AsyncCardDetailsResource"] + + +class CardDetailsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CardDetailsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return CardDetailsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CardDetailsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return CardDetailsResourceWithStreamingResponse(self) + + def retrieve( + self, + organization_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, + ) -> Optional[OrganizationBillingCardDetails]: + """ + Get card details for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/billing/card-details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingCardDetails, + ) + + +class AsyncCardDetailsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCardDetailsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncCardDetailsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCardDetailsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncCardDetailsResourceWithStreamingResponse(self) + + async def retrieve( + self, + organization_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, + ) -> Optional[OrganizationBillingCardDetails]: + """ + Get card details for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/billing/card-details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingCardDetails, + ) + + +class CardDetailsResourceWithRawResponse: + def __init__(self, card_details: CardDetailsResource) -> None: + self._card_details = card_details + + self.retrieve = to_raw_response_wrapper( + card_details.retrieve, + ) + + +class AsyncCardDetailsResourceWithRawResponse: + def __init__(self, card_details: AsyncCardDetailsResource) -> None: + self._card_details = card_details + + self.retrieve = async_to_raw_response_wrapper( + card_details.retrieve, + ) + + +class CardDetailsResourceWithStreamingResponse: + def __init__(self, card_details: CardDetailsResource) -> None: + self._card_details = card_details + + self.retrieve = to_streamed_response_wrapper( + card_details.retrieve, + ) + + +class AsyncCardDetailsResourceWithStreamingResponse: + def __init__(self, card_details: AsyncCardDetailsResource) -> None: + self._card_details = card_details + + self.retrieve = async_to_streamed_response_wrapper( + card_details.retrieve, + ) diff --git a/src/codex/resources/organizations/billing/plan_details.py b/src/codex/resources/organizations/billing/plan_details.py new file mode 100644 index 00000000..6ff726e2 --- /dev/null +++ b/src/codex/resources/organizations/billing/plan_details.py @@ -0,0 +1,167 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.organizations.billing.organization_billing_plan_details import OrganizationBillingPlanDetails + +__all__ = ["PlanDetailsResource", "AsyncPlanDetailsResource"] + + +class PlanDetailsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> PlanDetailsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return PlanDetailsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PlanDetailsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return PlanDetailsResourceWithStreamingResponse(self) + + def retrieve( + self, + organization_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, + ) -> OrganizationBillingPlanDetails: + """ + Get plan details for an organization. + + This includes the plan name, + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/billing/plan-details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingPlanDetails, + ) + + +class AsyncPlanDetailsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncPlanDetailsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncPlanDetailsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPlanDetailsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncPlanDetailsResourceWithStreamingResponse(self) + + async def retrieve( + self, + organization_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, + ) -> OrganizationBillingPlanDetails: + """ + Get plan details for an organization. + + This includes the plan name, + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/billing/plan-details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingPlanDetails, + ) + + +class PlanDetailsResourceWithRawResponse: + def __init__(self, plan_details: PlanDetailsResource) -> None: + self._plan_details = plan_details + + self.retrieve = to_raw_response_wrapper( + plan_details.retrieve, + ) + + +class AsyncPlanDetailsResourceWithRawResponse: + def __init__(self, plan_details: AsyncPlanDetailsResource) -> None: + self._plan_details = plan_details + + self.retrieve = async_to_raw_response_wrapper( + plan_details.retrieve, + ) + + +class PlanDetailsResourceWithStreamingResponse: + def __init__(self, plan_details: PlanDetailsResource) -> None: + self._plan_details = plan_details + + self.retrieve = to_streamed_response_wrapper( + plan_details.retrieve, + ) + + +class AsyncPlanDetailsResourceWithStreamingResponse: + def __init__(self, plan_details: AsyncPlanDetailsResource) -> None: + self._plan_details = plan_details + + self.retrieve = async_to_streamed_response_wrapper( + plan_details.retrieve, + ) diff --git a/src/codex/resources/organizations/billing/setup_intent.py b/src/codex/resources/organizations/billing/setup_intent.py new file mode 100644 index 00000000..ba915c6a --- /dev/null +++ b/src/codex/resources/organizations/billing/setup_intent.py @@ -0,0 +1,163 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.organizations.billing.organization_billing_setup_intent import OrganizationBillingSetupIntent + +__all__ = ["SetupIntentResource", "AsyncSetupIntentResource"] + + +class SetupIntentResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SetupIntentResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return SetupIntentResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SetupIntentResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return SetupIntentResourceWithStreamingResponse(self) + + def create( + self, + organization_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, + ) -> OrganizationBillingSetupIntent: + """ + Create a setup intent for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._post( + f"/api/organizations/{organization_id}/billing/setup-intent", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingSetupIntent, + ) + + +class AsyncSetupIntentResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSetupIntentResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncSetupIntentResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSetupIntentResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncSetupIntentResourceWithStreamingResponse(self) + + async def create( + self, + organization_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, + ) -> OrganizationBillingSetupIntent: + """ + Create a setup intent for an organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._post( + f"/api/organizations/{organization_id}/billing/setup-intent", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationBillingSetupIntent, + ) + + +class SetupIntentResourceWithRawResponse: + def __init__(self, setup_intent: SetupIntentResource) -> None: + self._setup_intent = setup_intent + + self.create = to_raw_response_wrapper( + setup_intent.create, + ) + + +class AsyncSetupIntentResourceWithRawResponse: + def __init__(self, setup_intent: AsyncSetupIntentResource) -> None: + self._setup_intent = setup_intent + + self.create = async_to_raw_response_wrapper( + setup_intent.create, + ) + + +class SetupIntentResourceWithStreamingResponse: + def __init__(self, setup_intent: SetupIntentResource) -> None: + self._setup_intent = setup_intent + + self.create = to_streamed_response_wrapper( + setup_intent.create, + ) + + +class AsyncSetupIntentResourceWithStreamingResponse: + def __init__(self, setup_intent: AsyncSetupIntentResource) -> None: + self._setup_intent = setup_intent + + self.create = async_to_streamed_response_wrapper( + setup_intent.create, + ) diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py index 73c2542a..025ecba4 100644 --- a/src/codex/resources/organizations/organizations.py +++ b/src/codex/resources/organizations/organizations.py @@ -4,14 +4,6 @@ import httpx -from .billing import ( - BillingResource, - AsyncBillingResource, - BillingResourceWithRawResponse, - AsyncBillingResourceWithRawResponse, - BillingResourceWithStreamingResponse, - AsyncBillingResourceWithStreamingResponse, -) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -22,6 +14,14 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options +from .billing.billing import ( + BillingResource, + AsyncBillingResource, + BillingResourceWithRawResponse, + AsyncBillingResourceWithRawResponse, + BillingResourceWithStreamingResponse, + AsyncBillingResourceWithStreamingResponse, +) from ...types.organization_schema_public import OrganizationSchemaPublic __all__ = ["OrganizationsResource", "AsyncOrganizationsResource"] diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py index d39b124a..87f647d3 100644 --- a/src/codex/resources/users/myself/api_key.py +++ b/src/codex/resources/users/myself/api_key.py @@ -13,6 +13,7 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) +from ....types.user import User from ...._base_client import make_request_options from ....types.users.user_schema import UserSchema @@ -39,6 +40,25 @@ def with_streaming_response(self) -> APIKeyResourceWithStreamingResponse: """ return APIKeyResourceWithStreamingResponse(self) + def retrieve( + self, + *, + # 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, + ) -> User: + """Get user when authenticated with API key.""" + return self._get( + "/api/users/myself/api-key", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=User, + ) + def refresh( self, *, @@ -79,6 +99,25 @@ def with_streaming_response(self) -> AsyncAPIKeyResourceWithStreamingResponse: """ return AsyncAPIKeyResourceWithStreamingResponse(self) + async def retrieve( + self, + *, + # 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, + ) -> User: + """Get user when authenticated with API key.""" + return await self._get( + "/api/users/myself/api-key", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=User, + ) + async def refresh( self, *, @@ -103,6 +142,9 @@ class APIKeyResourceWithRawResponse: def __init__(self, api_key: APIKeyResource) -> None: self._api_key = api_key + self.retrieve = to_raw_response_wrapper( + api_key.retrieve, + ) self.refresh = to_raw_response_wrapper( api_key.refresh, ) @@ -112,6 +154,9 @@ class AsyncAPIKeyResourceWithRawResponse: def __init__(self, api_key: AsyncAPIKeyResource) -> None: self._api_key = api_key + self.retrieve = async_to_raw_response_wrapper( + api_key.retrieve, + ) self.refresh = async_to_raw_response_wrapper( api_key.refresh, ) @@ -121,6 +166,9 @@ class APIKeyResourceWithStreamingResponse: def __init__(self, api_key: APIKeyResource) -> None: self._api_key = api_key + self.retrieve = to_streamed_response_wrapper( + api_key.retrieve, + ) self.refresh = to_streamed_response_wrapper( api_key.refresh, ) @@ -130,6 +178,9 @@ class AsyncAPIKeyResourceWithStreamingResponse: def __init__(self, api_key: AsyncAPIKeyResource) -> None: self._api_key = api_key + self.retrieve = async_to_streamed_response_wrapper( + api_key.retrieve, + ) self.refresh = async_to_streamed_response_wrapper( api_key.refresh, ) diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py index 3ee27229..2cc4868b 100644 --- a/src/codex/resources/users/myself/myself.py +++ b/src/codex/resources/users/myself/myself.py @@ -21,6 +21,7 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) +from ....types.user import User from .organizations import ( OrganizationsResource, AsyncOrganizationsResource, @@ -30,7 +31,6 @@ AsyncOrganizationsResourceWithStreamingResponse, ) from ...._base_client import make_request_options -from ....types.users.user_schema_public import UserSchemaPublic __all__ = ["MyselfResource", "AsyncMyselfResource"] @@ -72,14 +72,14 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> UserSchemaPublic: + ) -> User: """Get user info for frontend.""" return self._get( "/api/users/myself", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=UserSchemaPublic, + cast_to=User, ) @@ -120,14 +120,14 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> UserSchemaPublic: + ) -> User: """Get user info for frontend.""" return await self._get( "/api/users/myself", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=UserSchemaPublic, + cast_to=User, ) diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index 8a73b474..fb7ee0f1 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -2,8 +2,26 @@ from __future__ import annotations +from typing import Union, Optional +from datetime import datetime + +import httpx + +from ...types import user_activate_account_params +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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 ...types.user import User from .myself.myself import ( MyselfResource, AsyncMyselfResource, @@ -12,6 +30,7 @@ MyselfResourceWithStreamingResponse, AsyncMyselfResourceWithStreamingResponse, ) +from ..._base_client import make_request_options __all__ = ["UsersResource", "AsyncUsersResource"] @@ -40,6 +59,55 @@ def with_streaming_response(self) -> UsersResourceWithStreamingResponse: """ return UsersResourceWithStreamingResponse(self) + def activate_account( + self, + *, + first_name: str, + last_name: str, + account_activated_at: Union[str, datetime] | NotGiven = NOT_GIVEN, + discovery_source: Optional[str] | NotGiven = NOT_GIVEN, + is_account_activated: bool | NotGiven = NOT_GIVEN, + phone_number: Optional[str] | NotGiven = NOT_GIVEN, + user_provided_company_name: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> User: + """ + Activate an authenticated user's account + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._patch( + "/api/users/activate_account", + body=maybe_transform( + { + "first_name": first_name, + "last_name": last_name, + "account_activated_at": account_activated_at, + "discovery_source": discovery_source, + "is_account_activated": is_account_activated, + "phone_number": phone_number, + "user_provided_company_name": user_provided_company_name, + }, + user_activate_account_params.UserActivateAccountParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=User, + ) + class AsyncUsersResource(AsyncAPIResource): @cached_property @@ -65,11 +133,64 @@ def with_streaming_response(self) -> AsyncUsersResourceWithStreamingResponse: """ return AsyncUsersResourceWithStreamingResponse(self) + async def activate_account( + self, + *, + first_name: str, + last_name: str, + account_activated_at: Union[str, datetime] | NotGiven = NOT_GIVEN, + discovery_source: Optional[str] | NotGiven = NOT_GIVEN, + is_account_activated: bool | NotGiven = NOT_GIVEN, + phone_number: Optional[str] | NotGiven = NOT_GIVEN, + user_provided_company_name: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> User: + """ + Activate an authenticated user's account + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._patch( + "/api/users/activate_account", + body=await async_maybe_transform( + { + "first_name": first_name, + "last_name": last_name, + "account_activated_at": account_activated_at, + "discovery_source": discovery_source, + "is_account_activated": is_account_activated, + "phone_number": phone_number, + "user_provided_company_name": user_provided_company_name, + }, + user_activate_account_params.UserActivateAccountParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=User, + ) + class UsersResourceWithRawResponse: def __init__(self, users: UsersResource) -> None: self._users = users + self.activate_account = to_raw_response_wrapper( + users.activate_account, + ) + @cached_property def myself(self) -> MyselfResourceWithRawResponse: return MyselfResourceWithRawResponse(self._users.myself) @@ -79,6 +200,10 @@ class AsyncUsersResourceWithRawResponse: def __init__(self, users: AsyncUsersResource) -> None: self._users = users + self.activate_account = async_to_raw_response_wrapper( + users.activate_account, + ) + @cached_property def myself(self) -> AsyncMyselfResourceWithRawResponse: return AsyncMyselfResourceWithRawResponse(self._users.myself) @@ -88,6 +213,10 @@ class UsersResourceWithStreamingResponse: def __init__(self, users: UsersResource) -> None: self._users = users + self.activate_account = to_streamed_response_wrapper( + users.activate_account, + ) + @cached_property def myself(self) -> MyselfResourceWithStreamingResponse: return MyselfResourceWithStreamingResponse(self._users.myself) @@ -97,6 +226,10 @@ class AsyncUsersResourceWithStreamingResponse: def __init__(self, users: AsyncUsersResource) -> None: self._users = users + self.activate_account = async_to_streamed_response_wrapper( + users.activate_account, + ) + @cached_property def myself(self) -> AsyncMyselfResourceWithStreamingResponse: return AsyncMyselfResourceWithStreamingResponse(self._users.myself) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 9a02abc3..f7ec95b6 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from .user import User as User from .project_list_params import ProjectListParams as ProjectListParams from .health_check_response import HealthCheckResponse as HealthCheckResponse from .project_create_params import ProjectCreateParams as ProjectCreateParams @@ -9,3 +10,4 @@ from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema from .project_update_params import ProjectUpdateParams as ProjectUpdateParams from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic +from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams diff --git a/src/codex/types/organizations/billing/__init__.py b/src/codex/types/organizations/billing/__init__.py new file mode 100644 index 00000000..b6e94543 --- /dev/null +++ b/src/codex/types/organizations/billing/__init__.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .organization_billing_card_details import OrganizationBillingCardDetails as OrganizationBillingCardDetails +from .organization_billing_plan_details import OrganizationBillingPlanDetails as OrganizationBillingPlanDetails +from .organization_billing_setup_intent import OrganizationBillingSetupIntent as OrganizationBillingSetupIntent diff --git a/src/codex/types/organizations/billing/organization_billing_card_details.py b/src/codex/types/organizations/billing/organization_billing_card_details.py new file mode 100644 index 00000000..77576d5d --- /dev/null +++ b/src/codex/types/organizations/billing/organization_billing_card_details.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ...._models import BaseModel + +__all__ = ["OrganizationBillingCardDetails", "BillingDetails", "CardDetails"] + + +class BillingDetails(BaseModel): + address_city: Optional[str] = None + + address_country: str + + address_line1: Optional[str] = None + + address_line2: Optional[str] = None + + address_postal_code: str + + address_state: Optional[str] = None + + name: Optional[str] = None + + +class CardDetails(BaseModel): + brand: str + + exp_month: int + + exp_year: int + + last4: str + + +class OrganizationBillingCardDetails(BaseModel): + billing_details: BillingDetails + + card_details: CardDetails diff --git a/src/codex/types/organizations/billing/organization_billing_plan_details.py b/src/codex/types/organizations/billing/organization_billing_plan_details.py new file mode 100644 index 00000000..03cc176f --- /dev/null +++ b/src/codex/types/organizations/billing/organization_billing_plan_details.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ...._models import BaseModel + +__all__ = ["OrganizationBillingPlanDetails"] + + +class OrganizationBillingPlanDetails(BaseModel): + current_billing_period_credits_answers: float + + current_billing_period_credits_cents: float + + current_billing_period_total_cents: float + + current_billing_period_usage_answers: float + + current_billing_period_usage_cents: float + + invoice_end_date: datetime + + invoice_issue_date: datetime + + invoice_start_date: datetime + + name: str + + is_enterprise: Optional[bool] = None diff --git a/src/codex/types/organizations/billing/organization_billing_setup_intent.py b/src/codex/types/organizations/billing/organization_billing_setup_intent.py new file mode 100644 index 00000000..dfd07d6b --- /dev/null +++ b/src/codex/types/organizations/billing/organization_billing_setup_intent.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from ...._models import BaseModel + +__all__ = ["OrganizationBillingSetupIntent"] + + +class OrganizationBillingSetupIntent(BaseModel): + client_secret: str + + intent_id: str diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/user.py similarity index 80% rename from src/codex/types/users/user_schema_public.py rename to src/codex/types/user.py index 181113b0..3d7ec233 100644 --- a/src/codex/types/users/user_schema_public.py +++ b/src/codex/types/user.py @@ -2,12 +2,12 @@ from typing import Optional -from ..._models import BaseModel +from .._models import BaseModel -__all__ = ["UserSchemaPublic"] +__all__ = ["User"] -class UserSchemaPublic(BaseModel): +class User(BaseModel): id: str api_key: str diff --git a/src/codex/types/user_activate_account_params.py b/src/codex/types/user_activate_account_params.py new file mode 100644 index 00000000..195c2ca2 --- /dev/null +++ b/src/codex/types/user_activate_account_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 import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["UserActivateAccountParams"] + + +class UserActivateAccountParams(TypedDict, total=False): + first_name: Required[str] + + last_name: Required[str] + + account_activated_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + + discovery_source: Optional[str] + + is_account_activated: bool + + phone_number: Optional[str] + + user_provided_company_name: Optional[str] diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py index 85decc6b..4256bd7f 100644 --- a/src/codex/types/users/__init__.py +++ b/src/codex/types/users/__init__.py @@ -3,4 +3,3 @@ from __future__ import annotations from .user_schema import UserSchema as UserSchema -from .user_schema_public import UserSchemaPublic as UserSchemaPublic diff --git a/tests/api_resources/organizations/billing/__init__.py b/tests/api_resources/organizations/billing/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/organizations/billing/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/organizations/billing/test_card_details.py b/tests/api_resources/organizations/billing/test_card_details.py new file mode 100644 index 00000000..2fb71fc5 --- /dev/null +++ b/tests/api_resources/organizations/billing/test_card_details.py @@ -0,0 +1,106 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, Optional, cast + +import pytest + +from codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.organizations.billing import OrganizationBillingCardDetails + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCardDetails: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + card_detail = client.organizations.billing.card_details.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.organizations.billing.card_details.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_detail = response.parse() + assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.organizations.billing.card_details.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_detail = response.parse() + assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.billing.card_details.with_raw_response.retrieve( + "", + ) + + +class TestAsyncCardDetails: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + card_detail = await async_client.organizations.billing.card_details.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.billing.card_details.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_detail = await response.parse() + assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.billing.card_details.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_detail = await response.parse() + assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.billing.card_details.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/organizations/billing/test_plan_details.py b/tests/api_resources/organizations/billing/test_plan_details.py new file mode 100644 index 00000000..2fc1b810 --- /dev/null +++ b/tests/api_resources/organizations/billing/test_plan_details.py @@ -0,0 +1,106 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.organizations.billing import OrganizationBillingPlanDetails + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPlanDetails: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + plan_detail = client.organizations.billing.plan_details.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.organizations.billing.plan_details.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + plan_detail = response.parse() + assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.organizations.billing.plan_details.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + plan_detail = response.parse() + assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.billing.plan_details.with_raw_response.retrieve( + "", + ) + + +class TestAsyncPlanDetails: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + plan_detail = await async_client.organizations.billing.plan_details.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.billing.plan_details.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + plan_detail = await response.parse() + assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.billing.plan_details.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + plan_detail = await response.parse() + assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.billing.plan_details.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/organizations/billing/test_setup_intent.py b/tests/api_resources/organizations/billing/test_setup_intent.py new file mode 100644 index 00000000..3eb05fbc --- /dev/null +++ b/tests/api_resources/organizations/billing/test_setup_intent.py @@ -0,0 +1,106 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.organizations.billing import OrganizationBillingSetupIntent + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSetupIntent: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_create(self, client: Codex) -> None: + setup_intent = client.organizations.billing.setup_intent.create( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.organizations.billing.setup_intent.with_raw_response.create( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + setup_intent = response.parse() + assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.organizations.billing.setup_intent.with_streaming_response.create( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + setup_intent = response.parse() + assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_create(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.billing.setup_intent.with_raw_response.create( + "", + ) + + +class TestAsyncSetupIntent: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + setup_intent = await async_client.organizations.billing.setup_intent.create( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.billing.setup_intent.with_raw_response.create( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + setup_intent = await response.parse() + assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.billing.setup_intent.with_streaming_response.create( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + setup_intent = await response.parse() + assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_create(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.billing.setup_intent.with_raw_response.create( + "", + ) diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py new file mode 100644 index 00000000..7e78b99e --- /dev/null +++ b/tests/api_resources/test_users.py @@ -0,0 +1,125 @@ +# 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 codex import Codex, AsyncCodex +from codex.types import User +from tests.utils import assert_matches_type +from codex._utils import parse_datetime + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUsers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_activate_account(self, client: Codex) -> None: + user = client.users.activate_account( + first_name="first_name", + last_name="last_name", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_activate_account_with_all_params(self, client: Codex) -> None: + user = client.users.activate_account( + first_name="first_name", + last_name="last_name", + account_activated_at=parse_datetime("2019-12-27T18:11:19.117Z"), + discovery_source="discovery_source", + is_account_activated=True, + phone_number="phone_number", + user_provided_company_name="user_provided_company_name", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_activate_account(self, client: Codex) -> None: + response = client.users.with_raw_response.activate_account( + first_name="first_name", + last_name="last_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = response.parse() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_activate_account(self, client: Codex) -> None: + with client.users.with_streaming_response.activate_account( + first_name="first_name", + last_name="last_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = response.parse() + assert_matches_type(User, user, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncUsers: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_activate_account(self, async_client: AsyncCodex) -> None: + user = await async_client.users.activate_account( + first_name="first_name", + last_name="last_name", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_activate_account_with_all_params(self, async_client: AsyncCodex) -> None: + user = await async_client.users.activate_account( + first_name="first_name", + last_name="last_name", + account_activated_at=parse_datetime("2019-12-27T18:11:19.117Z"), + discovery_source="discovery_source", + is_account_activated=True, + phone_number="phone_number", + user_provided_company_name="user_provided_company_name", + ) + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_activate_account(self, async_client: AsyncCodex) -> None: + response = await async_client.users.with_raw_response.activate_account( + first_name="first_name", + last_name="last_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + user = await response.parse() + assert_matches_type(User, user, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_activate_account(self, async_client: AsyncCodex) -> None: + async with async_client.users.with_streaming_response.activate_account( + first_name="first_name", + last_name="last_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + user = await response.parse() + assert_matches_type(User, user, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index 68996997..a58477bc 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -8,6 +8,7 @@ import pytest from codex import Codex, AsyncCodex +from codex.types import User from tests.utils import assert_matches_type from codex.types.users import UserSchema @@ -17,6 +18,34 @@ class TestAPIKey: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + api_key = client.users.myself.api_key.retrieve() + assert_matches_type(User, api_key, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.users.myself.api_key.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = response.parse() + assert_matches_type(User, api_key, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.users.myself.api_key.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = response.parse() + assert_matches_type(User, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize def test_method_refresh(self, client: Codex) -> None: @@ -49,6 +78,34 @@ def test_streaming_response_refresh(self, client: Codex) -> None: class TestAsyncAPIKey: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + api_key = await async_client.users.myself.api_key.retrieve() + assert_matches_type(User, api_key, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.users.myself.api_key.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + api_key = await response.parse() + assert_matches_type(User, api_key, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.users.myself.api_key.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_key = await response.parse() + assert_matches_type(User, api_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip() @parametrize async def test_method_refresh(self, async_client: AsyncCodex) -> None: diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index 63123275..174f2cec 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -8,8 +8,8 @@ import pytest from codex import Codex, AsyncCodex +from codex.types import User from tests.utils import assert_matches_type -from codex.types.users import UserSchemaPublic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,7 +21,7 @@ class TestMyself: @parametrize def test_method_retrieve(self, client: Codex) -> None: myself = client.users.myself.retrieve() - assert_matches_type(UserSchemaPublic, myself, path=["response"]) + assert_matches_type(User, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -31,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = response.parse() - assert_matches_type(UserSchemaPublic, myself, path=["response"]) + assert_matches_type(User, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -41,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = response.parse() - assert_matches_type(UserSchemaPublic, myself, path=["response"]) + assert_matches_type(User, myself, path=["response"]) assert cast(Any, response.is_closed) is True @@ -53,7 +53,7 @@ class TestAsyncMyself: @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: myself = await async_client.users.myself.retrieve() - assert_matches_type(UserSchemaPublic, myself, path=["response"]) + assert_matches_type(User, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -63,7 +63,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = await response.parse() - assert_matches_type(UserSchemaPublic, myself, path=["response"]) + assert_matches_type(User, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -73,6 +73,6 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = await response.parse() - assert_matches_type(UserSchemaPublic, myself, path=["response"]) + assert_matches_type(User, myself, path=["response"]) assert cast(Any, response.is_closed) is True From 6cfe9d91d02ef6aa496f561780b7d1469af86d6c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 00:48:52 +0000 Subject: [PATCH 043/320] chore(internal): version bump (#45) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b5db7ce1..c373724d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.7" + ".": "0.1.0-alpha.8" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6aa0de59..636b3299 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.7" +version = "0.1.0-alpha.8" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 4fb336bd..9cbcc56a 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.7" # x-release-please-version +__version__ = "0.1.0-alpha.8" # x-release-please-version From a21c250f8e0abfbae0e8bc914bd4040c91ea49a4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:39:12 +0000 Subject: [PATCH 044/320] feat(api): remove bearer auth (#46) --- README.md | 6 +++--- src/codex/_client.py | 50 ++------------------------------------------ 2 files changed, 5 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 42ef0af5..b8e49de2 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ project_return_schema = client.projects.create( print(project_return_schema.id) ``` -While you can provide a `bearer_token` keyword argument, +While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `CODEX_BEARER_TOKEN="My Bearer Token"` to your `.env` file -so that your Bearer Token is not stored in source control. +to add `CODEX_API_KEY="My API Key"` to your `.env` file +so that your API Key is not stored in source control. ## Async usage diff --git a/src/codex/_client.py b/src/codex/_client.py index fc0c27bf..843f6a21 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -65,7 +65,6 @@ class Codex(SyncAPIClient): with_streaming_response: CodexWithStreamedResponse # client options - bearer_token: str | None api_key: str | None access_key: str | None @@ -74,7 +73,6 @@ class Codex(SyncAPIClient): def __init__( self, *, - bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, @@ -100,14 +98,9 @@ def __init__( """Construct a new synchronous Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `bearer_token` from `CODEX_BEARER_TOKEN` - `api_key` from `CODEX_API_KEY` - `access_key` from `CODEX_ACCESS_KEY` """ - if bearer_token is None: - bearer_token = os.environ.get("CODEX_BEARER_TOKEN") - self.bearer_token = bearer_token - if api_key is None: api_key = os.environ.get("CODEX_API_KEY") self.api_key = api_key @@ -168,21 +161,12 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - if self._http_bearer: - return self._http_bearer if self._authenticated_api_key: return self._authenticated_api_key if self._public_access_key: return self._public_access_key return {} - @property - def _http_bearer(self) -> dict[str, str]: - bearer_token = self.bearer_token - if bearer_token is None: - return {} - return {"Authorization": f"Bearer {bearer_token}"} - @property def _authenticated_api_key(self) -> dict[str, str]: api_key = self.api_key @@ -208,11 +192,6 @@ def default_headers(self) -> dict[str, str | Omit]: @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.bearer_token and headers.get("Authorization"): - return - if isinstance(custom_headers.get("Authorization"), Omit): - return - if self.api_key and headers.get("X-API-Key"): return if isinstance(custom_headers.get("X-API-Key"), Omit): @@ -224,13 +203,12 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: return raise TypeError( - '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' ) def copy( self, *, - bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | None = None, @@ -267,7 +245,6 @@ def copy( http_client = http_client or self._client return self.__class__( - bearer_token=bearer_token or self.bearer_token, api_key=api_key or self.api_key, access_key=access_key or self.access_key, base_url=base_url or self.base_url, @@ -327,7 +304,6 @@ class AsyncCodex(AsyncAPIClient): with_streaming_response: AsyncCodexWithStreamedResponse # client options - bearer_token: str | None api_key: str | None access_key: str | None @@ -336,7 +312,6 @@ class AsyncCodex(AsyncAPIClient): def __init__( self, *, - bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, @@ -362,14 +337,9 @@ def __init__( """Construct a new async Codex client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `bearer_token` from `CODEX_BEARER_TOKEN` - `api_key` from `CODEX_API_KEY` - `access_key` from `CODEX_ACCESS_KEY` """ - if bearer_token is None: - bearer_token = os.environ.get("CODEX_BEARER_TOKEN") - self.bearer_token = bearer_token - if api_key is None: api_key = os.environ.get("CODEX_API_KEY") self.api_key = api_key @@ -430,21 +400,12 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - if self._http_bearer: - return self._http_bearer if self._authenticated_api_key: return self._authenticated_api_key if self._public_access_key: return self._public_access_key return {} - @property - def _http_bearer(self) -> dict[str, str]: - bearer_token = self.bearer_token - if bearer_token is None: - return {} - return {"Authorization": f"Bearer {bearer_token}"} - @property def _authenticated_api_key(self) -> dict[str, str]: api_key = self.api_key @@ -470,11 +431,6 @@ def default_headers(self) -> dict[str, str | Omit]: @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.bearer_token and headers.get("Authorization"): - return - if isinstance(custom_headers.get("Authorization"), Omit): - return - if self.api_key and headers.get("X-API-Key"): return if isinstance(custom_headers.get("X-API-Key"), Omit): @@ -486,13 +442,12 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: return raise TypeError( - '"Could not resolve authentication method. Expected one of bearer_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' ) def copy( self, *, - bearer_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | None = None, @@ -529,7 +484,6 @@ def copy( http_client = http_client or self._client return self.__class__( - bearer_token=bearer_token or self.bearer_token, api_key=api_key or self.api_key, access_key=access_key or self.access_key, base_url=base_url or self.base_url, From 71aaab337ebf0902b39192a7fcdc4d89921ff32e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:50:00 +0000 Subject: [PATCH 045/320] feat(api): remove env var auth (#48) --- README.md | 5 ----- src/codex/_client.py | 22 ++-------------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b8e49de2..2695ffe2 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,6 @@ project_return_schema = client.projects.create( print(project_return_schema.id) ``` -While you can provide an `api_key` keyword argument, -we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `CODEX_API_KEY="My API Key"` to your `.env` file -so that your API Key is not stored in source control. - ## Async usage Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call: diff --git a/src/codex/_client.py b/src/codex/_client.py index 843f6a21..09aaafd1 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -95,18 +95,9 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous Codex client instance. - - This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `api_key` from `CODEX_API_KEY` - - `access_key` from `CODEX_ACCESS_KEY` - """ - if api_key is None: - api_key = os.environ.get("CODEX_API_KEY") + """Construct a new synchronous Codex client instance.""" self.api_key = api_key - if access_key is None: - access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment @@ -334,18 +325,9 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async Codex client instance. - - This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `api_key` from `CODEX_API_KEY` - - `access_key` from `CODEX_ACCESS_KEY` - """ - if api_key is None: - api_key = os.environ.get("CODEX_API_KEY") + """Construct a new async Codex client instance.""" self.api_key = api_key - if access_key is None: - access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment From f6ca515d734d5a08d34242ad5bdfb050232cf687 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:52:49 +0000 Subject: [PATCH 046/320] feat(api): add back env var auth (#49) --- README.md | 5 +++++ src/codex/_client.py | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2695ffe2..b8e49de2 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,11 @@ project_return_schema = client.projects.create( print(project_return_schema.id) ``` +While you can provide an `api_key` keyword argument, +we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) +to add `CODEX_API_KEY="My API Key"` to your `.env` file +so that your API Key is not stored in source control. + ## Async usage Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call: diff --git a/src/codex/_client.py b/src/codex/_client.py index 09aaafd1..843f6a21 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -95,9 +95,18 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous Codex client instance.""" + """Construct a new synchronous Codex client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `api_key` from `CODEX_API_KEY` + - `access_key` from `CODEX_ACCESS_KEY` + """ + if api_key is None: + api_key = os.environ.get("CODEX_API_KEY") self.api_key = api_key + if access_key is None: + access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment @@ -325,9 +334,18 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async Codex client instance.""" + """Construct a new async Codex client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `api_key` from `CODEX_API_KEY` + - `access_key` from `CODEX_ACCESS_KEY` + """ + if api_key is None: + api_key = os.environ.get("CODEX_API_KEY") self.api_key = api_key + if access_key is None: + access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment From 45e225c08f1e5ec8985aa19a06d17841f717fdf8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 20:02:13 +0000 Subject: [PATCH 047/320] feat(api): remove env var auth (#50) --- README.md | 5 ----- src/codex/_client.py | 22 ++-------------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b8e49de2..2695ffe2 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,6 @@ project_return_schema = client.projects.create( print(project_return_schema.id) ``` -While you can provide an `api_key` keyword argument, -we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `CODEX_API_KEY="My API Key"` to your `.env` file -so that your API Key is not stored in source control. - ## Async usage Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call: diff --git a/src/codex/_client.py b/src/codex/_client.py index 843f6a21..09aaafd1 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -95,18 +95,9 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous Codex client instance. - - This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `api_key` from `CODEX_API_KEY` - - `access_key` from `CODEX_ACCESS_KEY` - """ - if api_key is None: - api_key = os.environ.get("CODEX_API_KEY") + """Construct a new synchronous Codex client instance.""" self.api_key = api_key - if access_key is None: - access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment @@ -334,18 +325,9 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async Codex client instance. - - This automatically infers the following arguments from their corresponding environment variables if they are not provided: - - `api_key` from `CODEX_API_KEY` - - `access_key` from `CODEX_ACCESS_KEY` - """ - if api_key is None: - api_key = os.environ.get("CODEX_API_KEY") + """Construct a new async Codex client instance.""" self.api_key = api_key - if access_key is None: - access_key = os.environ.get("CODEX_ACCESS_KEY") self.access_key = access_key self._environment = environment From d4cde55c3bc3f208228e8902fc85b6f32d1b3a1c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 20:04:19 +0000 Subject: [PATCH 048/320] chore(internal): version bump (#51) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c373724d..46b9b6b2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.8" + ".": "0.1.0-alpha.9" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 636b3299..b38e6458 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.8" +version = "0.1.0-alpha.9" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 9cbcc56a..398bec65 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.8" # x-release-please-version +__version__ = "0.1.0-alpha.9" # x-release-please-version From 41d818a68b36a53535397bc9971a79c047af2c71 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:54:01 +0000 Subject: [PATCH 049/320] feat(api): update readme title (#53) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2695ffe2..1c5be89e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Codex Python API library +# Codex SDK API library [![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg)](https://pypi.org/project/codex-sdk/) -The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+ +The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+ 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). From 51195452af93181b4af7c5a955055c575d566b1b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:56:18 +0000 Subject: [PATCH 050/320] chore(internal): version bump (#54) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 46b9b6b2..3b005e52 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.9" + ".": "0.1.0-alpha.10" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b38e6458..7f4e270e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.9" +version = "0.1.0-alpha.10" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 398bec65..656b38ab 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.9" # x-release-please-version +__version__ = "0.1.0-alpha.10" # x-release-please-version From f9e60045c1e99bbddba37b5b4e188fe0652aa873 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 20:05:01 +0000 Subject: [PATCH 051/320] chore(internal): version bump (#58) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3b005e52..ee49ac2d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.10" + ".": "0.1.0-alpha.11" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7f4e270e..8648de69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.10" +version = "0.1.0-alpha.11" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 656b38ab..fd5f7081 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.10" # x-release-please-version +__version__ = "0.1.0-alpha.11" # x-release-please-version From 71a7631645a24fde4c7d9aec4304995004b1d52e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 20:30:25 +0000 Subject: [PATCH 052/320] feat(api): api update (#60) --- src/codex/resources/projects/entries.py | 16 ++++++++++++---- src/codex/types/projects/entry.py | 2 ++ src/codex/types/projects/entry_update_params.py | 2 ++ tests/api_resources/projects/test_entries.py | 2 ++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index d307d3d0..66762d51 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -138,6 +138,7 @@ def update( *, project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + frequency_count: Optional[int] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -167,6 +168,7 @@ def update( body=maybe_transform( { "answer": answer, + "frequency_count": frequency_count, "question": question, }, entry_update_params.EntryUpdateParams, @@ -318,8 +320,10 @@ def query( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Optional[Entry]: - """ - Query knowledge for a project. + """Query knowledge for a project. + + Also increments the frequency_count for the + matching entry if found. Returns the matching entry if found and answered, otherwise returns None. This allows the client to distinguish between: (1) no matching question found @@ -451,6 +455,7 @@ async def update( *, project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + frequency_count: Optional[int] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -480,6 +485,7 @@ async def update( body=await async_maybe_transform( { "answer": answer, + "frequency_count": frequency_count, "question": question, }, entry_update_params.EntryUpdateParams, @@ -631,8 +637,10 @@ async def query( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Optional[Entry]: - """ - Query knowledge for a project. + """Query knowledge for a project. + + Also increments the frequency_count for the + matching entry if found. Returns the matching entry if found and answered, otherwise returns None. This allows the client to distinguish between: (1) no matching question found diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index d3e1fc5f..4621cc4b 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -18,3 +18,5 @@ class Entry(BaseModel): answer: Optional[str] = None answered_at: Optional[datetime] = None + + frequency_count: Optional[int] = None diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py index 0a676f31..ba105495 100644 --- a/src/codex/types/projects/entry_update_params.py +++ b/src/codex/types/projects/entry_update_params.py @@ -13,4 +13,6 @@ class EntryUpdateParams(TypedDict, total=False): answer: Optional[str] + frequency_count: Optional[int] + question: Optional[str] diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 5b51ec12..026add46 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -144,6 +144,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", + frequency_count=0, question="question", ) assert_matches_type(Entry, entry, path=["response"]) @@ -519,6 +520,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", + frequency_count=0, question="question", ) assert_matches_type(Entry, entry, path=["response"]) From 5a9804219610fa14906d96b52fbe13a288ee6a0e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 03:09:51 +0000 Subject: [PATCH 053/320] chore(internal): change default timeout to an int (#61) --- src/codex/_constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_constants.py b/src/codex/_constants.py index a2ac3b6f..6ddf2c71 100644 --- a/src/codex/_constants.py +++ b/src/codex/_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) From 87571919f0720b870f175772560f75238ccdd699 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 03:11:26 +0000 Subject: [PATCH 054/320] chore(internal): bummp ruff dependency (#62) --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- scripts/utils/ruffen-docs.py | 4 ++-- src/codex/_models.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8648de69..7594a895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,7 +177,7 @@ select = [ "T201", "T203", # misuse of typing.TYPE_CHECKING - "TCH004", + "TC004", # import rules "TID251", ] diff --git a/requirements-dev.lock b/requirements-dev.lock index ef078719..1961e8d6 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -78,7 +78,7 @@ pytz==2023.3.post1 # via dirty-equals respx==0.22.0 rich==13.7.1 -ruff==0.6.9 +ruff==0.9.4 setuptools==68.2.2 # via nodeenv six==1.16.0 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/src/codex/_models.py b/src/codex/_models.py index 9a918aab..12c34b7d 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -172,7 +172,7 @@ 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. From a4a10aea0a54bdce753b5068f2ce3c611b62e8b9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:13:40 +0000 Subject: [PATCH 055/320] feat(client): send `X-Stainless-Read-Timeout` header (#63) --- src/codex/_base_client.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 9090e758..e908e40a 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -418,10 +418,17 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: headers[idempotency_header] = options.idempotency_key or self._idempotency_key() - # Don't set the retry count header if it was already set or removed by the caller. We check + # 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. - if "x-stainless-retry-count" not in (header.lower() for header in custom_headers): + 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 From 207ee5cf9e112f7f9f28db7f1385a5d55f5323db Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 03:12:20 +0000 Subject: [PATCH 056/320] chore(internal): fix type traversing dictionary params (#64) --- src/codex/_utils/_transform.py | 12 +++++++++++- tests/test_transform.py | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index a6b62cad..18afd9d8 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -25,7 +25,7 @@ is_annotated_type, strip_annotated_type, ) -from .._compat import model_dump, is_typeddict +from .._compat import get_origin, model_dump, is_typeddict _T = TypeVar("_T") @@ -164,9 +164,14 @@ def _transform_recursive( 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)) @@ -307,9 +312,14 @@ async def _async_transform_recursive( 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)) diff --git a/tests/test_transform.py b/tests/test_transform.py index 2e918885..324f31a7 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -2,7 +2,7 @@ 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 @@ -388,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")] From 23f52a8e244a616e99d5f00044610abfeaf35e92 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 03:15:15 +0000 Subject: [PATCH 057/320] chore(internal): minor type handling changes (#65) --- src/codex/_models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index 12c34b7d..c4401ff8 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -426,10 +426,16 @@ def construct_type(*, value: object, type_: object) -> object: 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` @@ -446,7 +452,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 From 6e8571f48e4e6ff8628ceae9b9cd392a2d83ccf4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 01:30:11 +0000 Subject: [PATCH 058/320] feat(api): api update (#67) --- src/codex/resources/projects/entries.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 66762d51..2fcc8e0b 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -70,6 +70,8 @@ def create( """ Create a knowledge entry for a project. + Raises: HTTPException: If an existing entry is found with the same question. + Args: extra_headers: Send extra headers @@ -387,6 +389,8 @@ async def create( """ Create a knowledge entry for a project. + Raises: HTTPException: If an existing entry is found with the same question. + Args: extra_headers: Send extra headers From 86b755bd78f8671d9a24100eb4c5cc5d455a7a43 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 03:36:01 +0000 Subject: [PATCH 059/320] chore(internal): update client tests (#68) --- tests/test_client.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index b421541f..0b0b783b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,10 +23,12 @@ from codex import Codex, AsyncCodex, APIResponseValidationError from codex._types import Omit +from codex._utils import maybe_transform from codex._models import BaseModel, FinalRequestOptions from codex._constants import RAW_RESPONSE_HEADER from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError from codex._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options +from codex.types.project_create_params import ProjectCreateParams from .utils import update_env @@ -680,7 +682,13 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No with pytest.raises(APITimeoutError): self.client.post( "/api/projects/", - body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + body=cast( + object, + maybe_transform( + dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), + ProjectCreateParams, + ), + ), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -695,7 +703,13 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non with pytest.raises(APIStatusError): self.client.post( "/api/projects/", - body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + body=cast( + object, + maybe_transform( + dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), + ProjectCreateParams, + ), + ), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -1425,7 +1439,13 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) with pytest.raises(APITimeoutError): await self.client.post( "/api/projects/", - body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + body=cast( + object, + maybe_transform( + dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), + ProjectCreateParams, + ), + ), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -1440,7 +1460,13 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) with pytest.raises(APIStatusError): await self.client.post( "/api/projects/", - body=cast(object, dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")), + body=cast( + object, + maybe_transform( + dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), + ProjectCreateParams, + ), + ), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) From 1b7608b3a892e325720237b222306498ec284b64 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 14 Feb 2025 03:24:26 +0000 Subject: [PATCH 060/320] fix: asyncify on non-asyncio runtimes (#69) --- src/codex/_utils/_sync.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/codex/_utils/_sync.py b/src/codex/_utils/_sync.py index 8b3aaf2b..ad7ec71b 100644 --- a/src/codex/_utils/_sync.py +++ b/src/codex/_utils/_sync.py @@ -7,16 +7,20 @@ from typing import Any, TypeVar, Callable, Awaitable from typing_extensions import ParamSpec +import anyio +import sniffio +import anyio.to_thread + T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") if sys.version_info >= (3, 9): - to_thread = asyncio.to_thread + _asyncio_to_thread = asyncio.to_thread else: # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread # for Python 3.8 support - async def to_thread( + async def _asyncio_to_thread( func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs ) -> Any: """Asynchronously run function *func* in a separate thread. @@ -34,6 +38,17 @@ async def to_thread( return await loop.run_in_executor(None, func_call) +async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs +) -> T_Retval: + if sniffio.current_async_library() == "asyncio": + return await _asyncio_to_thread(func, *args, **kwargs) + + return await 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]]: """ From 497d779396287f754e5e65125c12ef9c4a702416 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 04:33:53 +0000 Subject: [PATCH 061/320] feat(client): allow passing `NotGiven` for body (#70) fix(client): mark some request bodies as optional --- src/codex/_base_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index e908e40a..634d1be2 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -518,7 +518,7 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data, + json=json_data if is_given(json_data) else None, files=files, **kwargs, ) From 08d238bc62dbda6d4e643e2c2075402fced12fff Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 22 Feb 2025 03:37:58 +0000 Subject: [PATCH 062/320] chore(internal): fix devcontainers setup (#71) --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac9a2e75..55d20255 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,4 +6,4 @@ USER vscode RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.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. From 03126a48fa9ddb92544a323aa13855fc88229ac9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 03:14:51 +0000 Subject: [PATCH 063/320] chore(internal): properly set __pydantic_private__ (#72) --- src/codex/_base_client.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 634d1be2..cc521c2b 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -63,7 +63,7 @@ ModelBuilderProtocol, ) 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_V2, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -207,6 +207,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -292,6 +295,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options From 7ddb32ee6ac3e16eca46852a24a8b568a48fe33b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 03:05:04 +0000 Subject: [PATCH 064/320] docs: update URLs from stainlessapi.com to stainless.com (#73) More details at https://www.stainless.com/changelog/stainless-com --- README.md | 2 +- SECURITY.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1c5be89e..7a4f2e12 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Codex SDK library provides convenient access to the Codex REST API from any 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 diff --git a/SECURITY.md b/SECURITY.md index 54f64467..9fc6ee28 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 From 4caecec85d93c6005bd26d3a19c55f70c5d8b23f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 03:05:48 +0000 Subject: [PATCH 065/320] chore(docs): update client docstring (#75) --- src/codex/_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index 09aaafd1..deff2779 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -325,7 +325,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async Codex client instance.""" + """Construct a new async AsyncCodex client instance.""" self.api_key = api_key self.access_key = access_key From 1a07feeb4f8e734370d2818851a4f077d581f871 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 03:43:37 +0000 Subject: [PATCH 066/320] chore(internal): remove unused http client options forwarding (#76) --- src/codex/_base_client.py | 97 +-------------------------------------- 1 file changed, 1 insertion(+), 96 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index cc521c2b..273341b1 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -9,7 +9,6 @@ import inspect import logging import platform -import warnings import email.utils from types import TracebackType from random import random @@ -36,7 +35,7 @@ import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions @@ -51,13 +50,10 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, @@ -337,9 +333,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 @@ -352,9 +345,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: @@ -362,9 +352,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 @@ -800,46 +787,11 @@ def __init__( 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, 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: - kwargs: dict[str, Any] = {} - 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: - kwargs["transport"] = transport - 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: - kwargs["proxies"] = proxies - 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. @@ -860,12 +812,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, @@ -875,9 +824,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -1372,45 +1318,10 @@ def __init__( _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, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - kwargs: dict[str, Any] = {} - 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: - kwargs["transport"] = transport - 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: - kwargs["proxies"] = proxies - 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. @@ -1432,11 +1343,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, @@ -1446,9 +1354,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: From 08b9e6959b4621e6272e5c332dbe97e9bce7cd99 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 10:45:29 +0000 Subject: [PATCH 067/320] docs: revise readme docs about nested params (#77) --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 7a4f2e12..95452e72 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,23 @@ for entry in first_page.entries: # Remove `await` for non-async usage. ``` +## Nested params + +Nested parameters are dictionaries, typed using `TypedDict`, for example: + +```python +from codex import Codex + +client = Codex() + +project_return_schema = client.projects.create( + config={"max_distance": 0}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) +print(project_return_schema.config) +``` + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised. From e9b6186795a630a163d300125a26ea65df9d3866 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 10:59:01 +0000 Subject: [PATCH 068/320] test: add DEFER_PYDANTIC_BUILD=false flag to tests (#78) --- scripts/test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test b/scripts/test index 4fa5698b..2b878456 100755 --- a/scripts/test +++ b/scripts/test @@ -52,6 +52,8 @@ else echo fi +export DEFER_PYDANTIC_BUILD=false + echo "==> Running tests" rye run pytest "$@" From 502ace3fc76a71330e32c25b54b3a20cc016dc61 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:45:39 +0000 Subject: [PATCH 069/320] feat(api): add tlm routes (#79) --- .stats.yml | 2 +- api.md | 25 + src/codex/_client.py | 10 +- src/codex/resources/__init__.py | 14 + src/codex/resources/tlm.py | 656 ++++++++++++++++++ src/codex/resources/users/__init__.py | 14 + src/codex/resources/users/users.py | 32 + src/codex/resources/users/verification.py | 135 ++++ src/codex/types/__init__.py | 4 + src/codex/types/tlm_prompt_params.py | 127 ++++ src/codex/types/tlm_prompt_response.py | 15 + src/codex/types/tlm_score_params.py | 129 ++++ src/codex/types/tlm_score_response.py | 13 + src/codex/types/users/__init__.py | 1 + .../users/verification_resend_response.py | 8 + tests/api_resources/test_tlm.py | 254 +++++++ .../api_resources/users/test_verification.py | 78 +++ 17 files changed, 1515 insertions(+), 2 deletions(-) create mode 100644 src/codex/resources/tlm.py create mode 100644 src/codex/resources/users/verification.py create mode 100644 src/codex/types/tlm_prompt_params.py create mode 100644 src/codex/types/tlm_prompt_response.py create mode 100644 src/codex/types/tlm_score_params.py create mode 100644 src/codex/types/tlm_score_response.py create mode 100644 src/codex/types/users/verification_resend_response.py create mode 100644 tests/api_resources/test_tlm.py create mode 100644 tests/api_resources/users/test_verification.py diff --git a/.stats.yml b/.stats.yml index 966331a1..7982133f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 34 +configured_endpoints: 37 diff --git a/api.md b/api.md index b2fc2f00..5e258334 100644 --- a/api.md +++ b/api.md @@ -119,6 +119,18 @@ Methods: - client.users.myself.organizations.list() -> UserOrganizationsSchema +## Verification + +Types: + +```python +from codex.types.users import VerificationResendResponse +``` + +Methods: + +- client.users.verification.resend() -> VerificationResendResponse + # Projects Types: @@ -175,3 +187,16 @@ Methods: - client.projects.entries.delete(entry_id, \*, project_id) -> None - client.projects.entries.add_question(project_id, \*\*params) -> Entry - client.projects.entries.query(project_id, \*\*params) -> Optional[Entry] + +# Tlm + +Types: + +```python +from codex.types import TlmPromptResponse, TlmScoreResponse +``` + +Methods: + +- client.tlm.prompt(\*\*params) -> TlmPromptResponse +- client.tlm.score(\*\*params) -> TlmScoreResponse diff --git a/src/codex/_client.py b/src/codex/_client.py index deff2779..9bdd5f02 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -25,7 +25,7 @@ get_async_library, ) from ._version import __version__ -from .resources import health +from .resources import tlm, health from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError from ._base_client import ( @@ -61,6 +61,7 @@ class Codex(SyncAPIClient): organizations: organizations.OrganizationsResource users: users.UsersResource projects: projects.ProjectsResource + tlm: tlm.TlmResource with_raw_response: CodexWithRawResponse with_streaming_response: CodexWithStreamedResponse @@ -141,6 +142,7 @@ def __init__( self.organizations = organizations.OrganizationsResource(self) self.users = users.UsersResource(self) self.projects = projects.ProjectsResource(self) + self.tlm = tlm.TlmResource(self) self.with_raw_response = CodexWithRawResponse(self) self.with_streaming_response = CodexWithStreamedResponse(self) @@ -291,6 +293,7 @@ class AsyncCodex(AsyncAPIClient): organizations: organizations.AsyncOrganizationsResource users: users.AsyncUsersResource projects: projects.AsyncProjectsResource + tlm: tlm.AsyncTlmResource with_raw_response: AsyncCodexWithRawResponse with_streaming_response: AsyncCodexWithStreamedResponse @@ -371,6 +374,7 @@ def __init__( self.organizations = organizations.AsyncOrganizationsResource(self) self.users = users.AsyncUsersResource(self) self.projects = projects.AsyncProjectsResource(self) + self.tlm = tlm.AsyncTlmResource(self) self.with_raw_response = AsyncCodexWithRawResponse(self) self.with_streaming_response = AsyncCodexWithStreamedResponse(self) @@ -522,6 +526,7 @@ def __init__(self, client: Codex) -> None: self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations) self.users = users.UsersResourceWithRawResponse(client.users) self.projects = projects.ProjectsResourceWithRawResponse(client.projects) + self.tlm = tlm.TlmResourceWithRawResponse(client.tlm) class AsyncCodexWithRawResponse: @@ -530,6 +535,7 @@ def __init__(self, client: AsyncCodex) -> None: self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations) self.users = users.AsyncUsersResourceWithRawResponse(client.users) self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) + self.tlm = tlm.AsyncTlmResourceWithRawResponse(client.tlm) class CodexWithStreamedResponse: @@ -538,6 +544,7 @@ def __init__(self, client: Codex) -> None: self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.UsersResourceWithStreamingResponse(client.users) self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) + self.tlm = tlm.TlmResourceWithStreamingResponse(client.tlm) class AsyncCodexWithStreamedResponse: @@ -546,6 +553,7 @@ def __init__(self, client: AsyncCodex) -> None: self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.AsyncUsersResourceWithStreamingResponse(client.users) self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) + self.tlm = tlm.AsyncTlmResourceWithStreamingResponse(client.tlm) Client = Codex diff --git a/src/codex/resources/__init__.py b/src/codex/resources/__init__.py index b96b725a..f91f0e43 100644 --- a/src/codex/resources/__init__.py +++ b/src/codex/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .tlm import ( + TlmResource, + AsyncTlmResource, + TlmResourceWithRawResponse, + AsyncTlmResourceWithRawResponse, + TlmResourceWithStreamingResponse, + AsyncTlmResourceWithStreamingResponse, +) from .users import ( UsersResource, AsyncUsersResource, @@ -58,4 +66,10 @@ "AsyncProjectsResourceWithRawResponse", "ProjectsResourceWithStreamingResponse", "AsyncProjectsResourceWithStreamingResponse", + "TlmResource", + "AsyncTlmResource", + "TlmResourceWithRawResponse", + "AsyncTlmResourceWithRawResponse", + "TlmResourceWithStreamingResponse", + "AsyncTlmResourceWithStreamingResponse", ] diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py new file mode 100644 index 00000000..c6585d0d --- /dev/null +++ b/src/codex/resources/tlm.py @@ -0,0 +1,656 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal + +import httpx + +from ..types import tlm_score_params, tlm_prompt_params +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.tlm_score_response import TlmScoreResponse +from ..types.tlm_prompt_response import TlmPromptResponse + +__all__ = ["TlmResource", "AsyncTlmResource"] + + +class TlmResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> TlmResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return TlmResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TlmResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return TlmResourceWithStreamingResponse(self) + + def prompt( + self, + *, + prompt: str, + constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN, + quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + task: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> TlmPromptResponse: + """ + Prompts the TLM API. + + Args: + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", + "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o1", + "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", + "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", + "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", + "gpt-4o-mini". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. + TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). + This parameter must be between 1 and 20. + When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. + This consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it + generated and self-evaluate this response. + Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. + Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches answers that are obviously incorrect/bad. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures + similarity between sampled responses considered by the model in the consistency assessment. + Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), + "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). + Set this to "string" to improve latency/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) + when considering alternative possible responses and double-checking responses. + Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + 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._post( + "/api/tlm/prompt", + body=maybe_transform( + { + "prompt": prompt, + "constrain_outputs": constrain_outputs, + "options": options, + "quality_preset": quality_preset, + "task": task, + }, + tlm_prompt_params.TlmPromptParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TlmPromptResponse, + ) + + def score( + self, + *, + prompt: str, + response: str, + constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN, + quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + task: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> TlmScoreResponse: + """ + Scores the TLM API. + + TODO: + + - Track query count in DB + - Enforce hard cap on queries for users w/o credit card on file + + Args: + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", + "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o1", + "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", + "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", + "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", + "gpt-4o-mini". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. + TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). + This parameter must be between 1 and 20. + When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. + This consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it + generated and self-evaluate this response. + Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. + Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches answers that are obviously incorrect/bad. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures + similarity between sampled responses considered by the model in the consistency assessment. + Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), + "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). + Set this to "string" to improve latency/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) + when considering alternative possible responses and double-checking responses. + Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + 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._post( + "/api/tlm/score", + body=maybe_transform( + { + "prompt": prompt, + "response": response, + "constrain_outputs": constrain_outputs, + "options": options, + "quality_preset": quality_preset, + "task": task, + }, + tlm_score_params.TlmScoreParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TlmScoreResponse, + ) + + +class AsyncTlmResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncTlmResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncTlmResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTlmResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncTlmResourceWithStreamingResponse(self) + + async def prompt( + self, + *, + prompt: str, + constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN, + quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + task: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> TlmPromptResponse: + """ + Prompts the TLM API. + + Args: + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", + "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o1", + "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", + "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", + "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", + "gpt-4o-mini". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. + TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). + This parameter must be between 1 and 20. + When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. + This consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it + generated and self-evaluate this response. + Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. + Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches answers that are obviously incorrect/bad. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures + similarity between sampled responses considered by the model in the consistency assessment. + Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), + "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). + Set this to "string" to improve latency/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) + when considering alternative possible responses and double-checking responses. + Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + 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._post( + "/api/tlm/prompt", + body=await async_maybe_transform( + { + "prompt": prompt, + "constrain_outputs": constrain_outputs, + "options": options, + "quality_preset": quality_preset, + "task": task, + }, + tlm_prompt_params.TlmPromptParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TlmPromptResponse, + ) + + async def score( + self, + *, + prompt: str, + response: str, + constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN, + quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + task: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> TlmScoreResponse: + """ + Scores the TLM API. + + TODO: + + - Track query count in DB + - Enforce hard cap on queries for users w/o credit card on file + + Args: + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", + "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o1", + "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", + "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", + "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", + "gpt-4o-mini". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. + TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). + This parameter must be between 1 and 20. + When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. + This consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it + generated and self-evaluate this response. + Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. + Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches answers that are obviously incorrect/bad. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures + similarity between sampled responses considered by the model in the consistency assessment. + Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), + "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). + Set this to "string" to improve latency/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) + when considering alternative possible responses and double-checking responses. + Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + 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._post( + "/api/tlm/score", + body=await async_maybe_transform( + { + "prompt": prompt, + "response": response, + "constrain_outputs": constrain_outputs, + "options": options, + "quality_preset": quality_preset, + "task": task, + }, + tlm_score_params.TlmScoreParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TlmScoreResponse, + ) + + +class TlmResourceWithRawResponse: + def __init__(self, tlm: TlmResource) -> None: + self._tlm = tlm + + self.prompt = to_raw_response_wrapper( + tlm.prompt, + ) + self.score = to_raw_response_wrapper( + tlm.score, + ) + + +class AsyncTlmResourceWithRawResponse: + def __init__(self, tlm: AsyncTlmResource) -> None: + self._tlm = tlm + + self.prompt = async_to_raw_response_wrapper( + tlm.prompt, + ) + self.score = async_to_raw_response_wrapper( + tlm.score, + ) + + +class TlmResourceWithStreamingResponse: + def __init__(self, tlm: TlmResource) -> None: + self._tlm = tlm + + self.prompt = to_streamed_response_wrapper( + tlm.prompt, + ) + self.score = to_streamed_response_wrapper( + tlm.score, + ) + + +class AsyncTlmResourceWithStreamingResponse: + def __init__(self, tlm: AsyncTlmResource) -> None: + self._tlm = tlm + + self.prompt = async_to_streamed_response_wrapper( + tlm.prompt, + ) + self.score = async_to_streamed_response_wrapper( + tlm.score, + ) diff --git a/src/codex/resources/users/__init__.py b/src/codex/resources/users/__init__.py index 18ed37eb..9618f588 100644 --- a/src/codex/resources/users/__init__.py +++ b/src/codex/resources/users/__init__.py @@ -16,6 +16,14 @@ MyselfResourceWithStreamingResponse, AsyncMyselfResourceWithStreamingResponse, ) +from .verification import ( + VerificationResource, + AsyncVerificationResource, + VerificationResourceWithRawResponse, + AsyncVerificationResourceWithRawResponse, + VerificationResourceWithStreamingResponse, + AsyncVerificationResourceWithStreamingResponse, +) __all__ = [ "MyselfResource", @@ -24,6 +32,12 @@ "AsyncMyselfResourceWithRawResponse", "MyselfResourceWithStreamingResponse", "AsyncMyselfResourceWithStreamingResponse", + "VerificationResource", + "AsyncVerificationResource", + "VerificationResourceWithRawResponse", + "AsyncVerificationResourceWithRawResponse", + "VerificationResourceWithStreamingResponse", + "AsyncVerificationResourceWithStreamingResponse", "UsersResource", "AsyncUsersResource", "UsersResourceWithRawResponse", diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index fb7ee0f1..a7d9d2ab 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -22,6 +22,14 @@ async_to_streamed_response_wrapper, ) from ...types.user import User +from .verification import ( + VerificationResource, + AsyncVerificationResource, + VerificationResourceWithRawResponse, + AsyncVerificationResourceWithRawResponse, + VerificationResourceWithStreamingResponse, + AsyncVerificationResourceWithStreamingResponse, +) from .myself.myself import ( MyselfResource, AsyncMyselfResource, @@ -40,6 +48,10 @@ class UsersResource(SyncAPIResource): def myself(self) -> MyselfResource: return MyselfResource(self._client) + @cached_property + def verification(self) -> VerificationResource: + return VerificationResource(self._client) + @cached_property def with_raw_response(self) -> UsersResourceWithRawResponse: """ @@ -114,6 +126,10 @@ class AsyncUsersResource(AsyncAPIResource): def myself(self) -> AsyncMyselfResource: return AsyncMyselfResource(self._client) + @cached_property + def verification(self) -> AsyncVerificationResource: + return AsyncVerificationResource(self._client) + @cached_property def with_raw_response(self) -> AsyncUsersResourceWithRawResponse: """ @@ -195,6 +211,10 @@ def __init__(self, users: UsersResource) -> None: def myself(self) -> MyselfResourceWithRawResponse: return MyselfResourceWithRawResponse(self._users.myself) + @cached_property + def verification(self) -> VerificationResourceWithRawResponse: + return VerificationResourceWithRawResponse(self._users.verification) + class AsyncUsersResourceWithRawResponse: def __init__(self, users: AsyncUsersResource) -> None: @@ -208,6 +228,10 @@ def __init__(self, users: AsyncUsersResource) -> None: def myself(self) -> AsyncMyselfResourceWithRawResponse: return AsyncMyselfResourceWithRawResponse(self._users.myself) + @cached_property + def verification(self) -> AsyncVerificationResourceWithRawResponse: + return AsyncVerificationResourceWithRawResponse(self._users.verification) + class UsersResourceWithStreamingResponse: def __init__(self, users: UsersResource) -> None: @@ -221,6 +245,10 @@ def __init__(self, users: UsersResource) -> None: def myself(self) -> MyselfResourceWithStreamingResponse: return MyselfResourceWithStreamingResponse(self._users.myself) + @cached_property + def verification(self) -> VerificationResourceWithStreamingResponse: + return VerificationResourceWithStreamingResponse(self._users.verification) + class AsyncUsersResourceWithStreamingResponse: def __init__(self, users: AsyncUsersResource) -> None: @@ -233,3 +261,7 @@ def __init__(self, users: AsyncUsersResource) -> None: @cached_property def myself(self) -> AsyncMyselfResourceWithStreamingResponse: return AsyncMyselfResourceWithStreamingResponse(self._users.myself) + + @cached_property + def verification(self) -> AsyncVerificationResourceWithStreamingResponse: + return AsyncVerificationResourceWithStreamingResponse(self._users.verification) diff --git a/src/codex/resources/users/verification.py b/src/codex/resources/users/verification.py new file mode 100644 index 00000000..e75326e1 --- /dev/null +++ b/src/codex/resources/users/verification.py @@ -0,0 +1,135 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.users.verification_resend_response import VerificationResendResponse + +__all__ = ["VerificationResource", "AsyncVerificationResource"] + + +class VerificationResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VerificationResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return VerificationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VerificationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return VerificationResourceWithStreamingResponse(self) + + def resend( + self, + *, + # 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, + ) -> VerificationResendResponse: + """Resend verification email to the specified user through Auth0.""" + return self._post( + "/api/users/verification/resend", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VerificationResendResponse, + ) + + +class AsyncVerificationResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVerificationResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncVerificationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVerificationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncVerificationResourceWithStreamingResponse(self) + + async def resend( + self, + *, + # 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, + ) -> VerificationResendResponse: + """Resend verification email to the specified user through Auth0.""" + return await self._post( + "/api/users/verification/resend", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VerificationResendResponse, + ) + + +class VerificationResourceWithRawResponse: + def __init__(self, verification: VerificationResource) -> None: + self._verification = verification + + self.resend = to_raw_response_wrapper( + verification.resend, + ) + + +class AsyncVerificationResourceWithRawResponse: + def __init__(self, verification: AsyncVerificationResource) -> None: + self._verification = verification + + self.resend = async_to_raw_response_wrapper( + verification.resend, + ) + + +class VerificationResourceWithStreamingResponse: + def __init__(self, verification: VerificationResource) -> None: + self._verification = verification + + self.resend = to_streamed_response_wrapper( + verification.resend, + ) + + +class AsyncVerificationResourceWithStreamingResponse: + def __init__(self, verification: AsyncVerificationResource) -> None: + self._verification = verification + + self.resend = async_to_streamed_response_wrapper( + verification.resend, + ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index f7ec95b6..8f241bc9 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -3,7 +3,11 @@ from __future__ import annotations from .user import User as User +from .tlm_score_params import TlmScoreParams as TlmScoreParams +from .tlm_prompt_params import TlmPromptParams as TlmPromptParams +from .tlm_score_response import TlmScoreResponse as TlmScoreResponse from .project_list_params import ProjectListParams as ProjectListParams +from .tlm_prompt_response import TlmPromptResponse as TlmPromptResponse from .health_check_response import HealthCheckResponse as HealthCheckResponse from .project_create_params import ProjectCreateParams as ProjectCreateParams from .project_list_response import ProjectListResponse as ProjectListResponse diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py new file mode 100644 index 00000000..860f1a77 --- /dev/null +++ b/src/codex/types/tlm_prompt_params.py @@ -0,0 +1,127 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["TlmPromptParams", "Options"] + + +class TlmPromptParams(TypedDict, total=False): + prompt: Required[str] + + constrain_outputs: Optional[List[str]] + + options: Optional[Options] + """ + Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", + "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o1", + "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", + "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", + "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", + "gpt-4o-mini". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. + TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). + This parameter must be between 1 and 20. + When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. + This consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it + generated and self-evaluate this response. + Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. + Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches answers that are obviously incorrect/bad. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures + similarity between sampled responses considered by the model in the consistency assessment. + Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), + "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). + Set this to "string" to improve latency/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) + when considering alternative possible responses and double-checking responses. + Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + """ + + quality_preset: Literal["best", "high", "medium", "low", "base"] + + task: Optional[str] + + +class Options(TypedDict, total=False): + custom_eval_criteria: Iterable[object] + + log: List[str] + + max_tokens: int + + model: str + + num_candidate_responses: int + + num_consistency_samples: int + + reasoning_effort: str + + similarity_measure: str + + use_self_reflection: bool diff --git a/src/codex/types/tlm_prompt_response.py b/src/codex/types/tlm_prompt_response.py new file mode 100644 index 00000000..d939c00e --- /dev/null +++ b/src/codex/types/tlm_prompt_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["TlmPromptResponse"] + + +class TlmPromptResponse(BaseModel): + response: str + + trustworthiness_score: float + + log: Optional[object] = None diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py new file mode 100644 index 00000000..213da422 --- /dev/null +++ b/src/codex/types/tlm_score_params.py @@ -0,0 +1,129 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["TlmScoreParams", "Options"] + + +class TlmScoreParams(TypedDict, total=False): + prompt: Required[str] + + response: Required[str] + + constrain_outputs: Optional[List[str]] + + options: Optional[Options] + """ + Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", + "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o1", + "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", + "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", + "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", + "gpt-4o-mini". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. + TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). + This parameter must be between 1 and 20. + When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. + This consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it + generated and self-evaluate this response. + Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. + Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches answers that are obviously incorrect/bad. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures + similarity between sampled responses considered by the model in the consistency assessment. + Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), + "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). + Set this to "string" to improve latency/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) + when considering alternative possible responses and double-checking responses. + Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + """ + + quality_preset: Literal["best", "high", "medium", "low", "base"] + + task: Optional[str] + + +class Options(TypedDict, total=False): + custom_eval_criteria: Iterable[object] + + log: List[str] + + max_tokens: int + + model: str + + num_candidate_responses: int + + num_consistency_samples: int + + reasoning_effort: str + + similarity_measure: str + + use_self_reflection: bool diff --git a/src/codex/types/tlm_score_response.py b/src/codex/types/tlm_score_response.py new file mode 100644 index 00000000..e92b2e09 --- /dev/null +++ b/src/codex/types/tlm_score_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["TlmScoreResponse"] + + +class TlmScoreResponse(BaseModel): + trustworthiness_score: float + + log: Optional[object] = None diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py index 4256bd7f..438bc6f3 100644 --- a/src/codex/types/users/__init__.py +++ b/src/codex/types/users/__init__.py @@ -3,3 +3,4 @@ from __future__ import annotations from .user_schema import UserSchema as UserSchema +from .verification_resend_response import VerificationResendResponse as VerificationResendResponse diff --git a/src/codex/types/users/verification_resend_response.py b/src/codex/types/users/verification_resend_response.py new file mode 100644 index 00000000..6617ff54 --- /dev/null +++ b/src/codex/types/users/verification_resend_response.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict +from typing_extensions import TypeAlias + +__all__ = ["VerificationResendResponse"] + +VerificationResendResponse: TypeAlias = Dict[str, str] diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py new file mode 100644 index 00000000..32d5a67f --- /dev/null +++ b/tests/api_resources/test_tlm.py @@ -0,0 +1,254 @@ +# 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 codex import Codex, AsyncCodex +from codex.types import TlmScoreResponse, TlmPromptResponse +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestTlm: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_prompt(self, client: Codex) -> None: + tlm = client.tlm.prompt( + prompt="prompt", + ) + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_prompt_with_all_params(self, client: Codex) -> None: + tlm = client.tlm.prompt( + prompt="prompt", + constrain_outputs=["string"], + options={ + "custom_eval_criteria": [{}], + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + task="task", + ) + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_prompt(self, client: Codex) -> None: + response = client.tlm.with_raw_response.prompt( + prompt="prompt", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tlm = response.parse() + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_prompt(self, client: Codex) -> None: + with client.tlm.with_streaming_response.prompt( + prompt="prompt", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tlm = response.parse() + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_score(self, client: Codex) -> None: + tlm = client.tlm.score( + prompt="prompt", + response="response", + ) + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_score_with_all_params(self, client: Codex) -> None: + tlm = client.tlm.score( + prompt="prompt", + response="response", + constrain_outputs=["string"], + options={ + "custom_eval_criteria": [{}], + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + task="task", + ) + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_score(self, client: Codex) -> None: + response = client.tlm.with_raw_response.score( + prompt="prompt", + response="response", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tlm = response.parse() + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_score(self, client: Codex) -> None: + with client.tlm.with_streaming_response.score( + prompt="prompt", + response="response", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tlm = response.parse() + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncTlm: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_prompt(self, async_client: AsyncCodex) -> None: + tlm = await async_client.tlm.prompt( + prompt="prompt", + ) + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> None: + tlm = await async_client.tlm.prompt( + prompt="prompt", + constrain_outputs=["string"], + options={ + "custom_eval_criteria": [{}], + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + task="task", + ) + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_prompt(self, async_client: AsyncCodex) -> None: + response = await async_client.tlm.with_raw_response.prompt( + prompt="prompt", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tlm = await response.parse() + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_prompt(self, async_client: AsyncCodex) -> None: + async with async_client.tlm.with_streaming_response.prompt( + prompt="prompt", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tlm = await response.parse() + assert_matches_type(TlmPromptResponse, tlm, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_score(self, async_client: AsyncCodex) -> None: + tlm = await async_client.tlm.score( + prompt="prompt", + response="response", + ) + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> None: + tlm = await async_client.tlm.score( + prompt="prompt", + response="response", + constrain_outputs=["string"], + options={ + "custom_eval_criteria": [{}], + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + task="task", + ) + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_score(self, async_client: AsyncCodex) -> None: + response = await async_client.tlm.with_raw_response.score( + prompt="prompt", + response="response", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tlm = await response.parse() + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_score(self, async_client: AsyncCodex) -> None: + async with async_client.tlm.with_streaming_response.score( + prompt="prompt", + response="response", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tlm = await response.parse() + assert_matches_type(TlmScoreResponse, tlm, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/test_verification.py b/tests/api_resources/users/test_verification.py new file mode 100644 index 00000000..8332327e --- /dev/null +++ b/tests/api_resources/users/test_verification.py @@ -0,0 +1,78 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.types.users import VerificationResendResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestVerification: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_resend(self, client: Codex) -> None: + verification = client.users.verification.resend() + assert_matches_type(VerificationResendResponse, verification, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_resend(self, client: Codex) -> None: + response = client.users.verification.with_raw_response.resend() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = response.parse() + assert_matches_type(VerificationResendResponse, verification, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_resend(self, client: Codex) -> None: + with client.users.verification.with_streaming_response.resend() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = response.parse() + assert_matches_type(VerificationResendResponse, verification, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncVerification: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_resend(self, async_client: AsyncCodex) -> None: + verification = await async_client.users.verification.resend() + assert_matches_type(VerificationResendResponse, verification, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_resend(self, async_client: AsyncCodex) -> None: + response = await async_client.users.verification.with_raw_response.resend() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + verification = await response.parse() + assert_matches_type(VerificationResendResponse, verification, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_resend(self, async_client: AsyncCodex) -> None: + async with async_client.users.verification.with_streaming_response.resend() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + verification = await response.parse() + assert_matches_type(VerificationResendResponse, verification, path=["response"]) + + assert cast(Any, response.is_closed) is True From 317bef6441a5f59ab99151bba9aec66d95862e1a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:58:25 +0000 Subject: [PATCH 070/320] chore(internal): version bump (#81) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ee49ac2d..fd0ccba9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.11" + ".": "0.1.0-alpha.12" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7594a895..82e32b0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.11" +version = "0.1.0-alpha.12" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index fd5f7081..fe7cc739 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.11" # x-release-please-version +__version__ = "0.1.0-alpha.12" # x-release-please-version From bf43bf1e2d241de642f4736493f1ea7f592f3512 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 21:29:05 +0000 Subject: [PATCH 071/320] feat(api): api update (#83) --- src/codex/resources/projects/entries.py | 14 +++++++++++++- src/codex/types/projects/entry.py | 7 +++++++ src/codex/types/projects/entry_create_params.py | 2 ++ src/codex/types/projects/entry_list_params.py | 3 +++ src/codex/types/projects/entry_update_params.py | 2 ++ tests/api_resources/projects/test_entries.py | 6 ++++++ 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 2fcc8e0b..e2c16547 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import List, Optional from typing_extensions import Literal import httpx @@ -60,6 +60,7 @@ def create( *, question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + draft_answer: Optional[str] | NotGiven = NOT_GIVEN, # 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, @@ -89,6 +90,7 @@ def create( { "question": question, "answer": answer, + "draft_answer": draft_answer, }, entry_create_params.EntryCreateParams, ), @@ -140,6 +142,7 @@ def update( *, project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + draft_answer: Optional[str] | NotGiven = NOT_GIVEN, frequency_count: Optional[int] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -170,6 +173,7 @@ def update( body=maybe_transform( { "answer": answer, + "draft_answer": draft_answer, "frequency_count": frequency_count, "question": question, }, @@ -190,6 +194,7 @@ def list( offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, + states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, unanswered_only: bool | NotGiven = NOT_GIVEN, # 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. @@ -227,6 +232,7 @@ def list( "offset": offset, "order": order, "sort": sort, + "states": states, "unanswered_only": unanswered_only, }, entry_list_params.EntryListParams, @@ -379,6 +385,7 @@ async def create( *, question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + draft_answer: Optional[str] | NotGiven = NOT_GIVEN, # 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, @@ -408,6 +415,7 @@ async def create( { "question": question, "answer": answer, + "draft_answer": draft_answer, }, entry_create_params.EntryCreateParams, ), @@ -459,6 +467,7 @@ async def update( *, project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + draft_answer: Optional[str] | NotGiven = NOT_GIVEN, frequency_count: Optional[int] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -489,6 +498,7 @@ async def update( body=await async_maybe_transform( { "answer": answer, + "draft_answer": draft_answer, "frequency_count": frequency_count, "question": question, }, @@ -509,6 +519,7 @@ def list( offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, + states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, unanswered_only: bool | NotGiven = NOT_GIVEN, # 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. @@ -546,6 +557,7 @@ def list( "offset": offset, "order": order, "sort": sort, + "states": states, "unanswered_only": unanswered_only, }, entry_list_params.EntryListParams, diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index 4621cc4b..7a2c4502 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -2,6 +2,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from ..._models import BaseModel @@ -13,10 +14,16 @@ class Entry(BaseModel): question: str + state: Literal["unanswered", "draft", "published", "published_with_draft"] + id: Optional[str] = None answer: Optional[str] = None answered_at: Optional[datetime] = None + draft_answer: Optional[str] = None + + draft_answer_last_edited: Optional[datetime] = None + frequency_count: Optional[int] = None diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py index 1ac23dd4..daf30897 100644 --- a/src/codex/types/projects/entry_create_params.py +++ b/src/codex/types/projects/entry_create_params.py @@ -12,3 +12,5 @@ class EntryCreateParams(TypedDict, total=False): question: Required[str] answer: Optional[str] + + draft_answer: Optional[str] diff --git a/src/codex/types/projects/entry_list_params.py b/src/codex/types/projects/entry_list_params.py index b50181f7..605ea482 100644 --- a/src/codex/types/projects/entry_list_params.py +++ b/src/codex/types/projects/entry_list_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import List from typing_extensions import Literal, TypedDict __all__ = ["EntryListParams"] @@ -18,4 +19,6 @@ class EntryListParams(TypedDict, total=False): sort: Literal["created_at", "answered_at"] + states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] + unanswered_only: bool diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py index ba105495..05a6332d 100644 --- a/src/codex/types/projects/entry_update_params.py +++ b/src/codex/types/projects/entry_update_params.py @@ -13,6 +13,8 @@ class EntryUpdateParams(TypedDict, total=False): answer: Optional[str] + draft_answer: Optional[str] + frequency_count: Optional[int] question: Optional[str] diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 026add46..fbbf6f25 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -36,6 +36,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", answer="answer", + draft_answer="draft_answer", ) assert_matches_type(Entry, entry, path=["response"]) @@ -144,6 +145,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", + draft_answer="draft_answer", frequency_count=0, question="question", ) @@ -210,6 +212,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: offset=0, order="asc", sort="created_at", + states=["unanswered"], unanswered_only=True, ) assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) @@ -412,6 +415,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", answer="answer", + draft_answer="draft_answer", ) assert_matches_type(Entry, entry, path=["response"]) @@ -520,6 +524,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", + draft_answer="draft_answer", frequency_count=0, question="question", ) @@ -586,6 +591,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No offset=0, order="asc", sort="created_at", + states=["unanswered"], unanswered_only=True, ) assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) From 08855389f32cb1786efdf789f6f44b786ee8ffe5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 14 Mar 2025 08:05:45 +0000 Subject: [PATCH 072/320] chore(internal): remove extra empty newlines (#84) --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 82e32b0a..e2e84f14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ Homepage = "https://github.com/cleanlab/codex-python" Repository = "https://github.com/cleanlab/codex-python" - [tool.rye] managed = true # version pins are in requirements-dev.lock @@ -152,7 +151,6 @@ reportImplicitOverride = true reportImportCycles = false reportPrivateUsage = false - [tool.ruff] line-length = 120 output-format = "grouped" From 2a2b9db65825719141934848b2da87a24b7250b0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 06:29:25 +0000 Subject: [PATCH 073/320] chore(internal): codegen related update (#85) --- requirements-dev.lock | 1 + requirements.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements-dev.lock b/requirements-dev.lock index 1961e8d6..5b7189d6 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.6.0 diff --git a/requirements.lock b/requirements.lock index 5d7fff82..1f6c5f7f 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.6.0 From 4abff16a96af4e0b964d4e1a0b2a7831ef859314 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 06:32:58 +0000 Subject: [PATCH 074/320] chore(internal): bump rye to 0.44.0 (#86) --- .devcontainer/Dockerfile | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 55d20255..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 || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e503784c..5ac5f63f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: 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 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 5ed611c7..7c078aa3 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -21,7 +21,7 @@ jobs: 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 From 1a1166db743a1447d98e21aaa8dfb2c2ef84804a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 06:37:06 +0000 Subject: [PATCH 075/320] fix(types): handle more discriminated union shapes (#87) --- src/codex/_models.py | 7 +++++-- tests/test_models.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index c4401ff8..b51a1bf5 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -65,7 +65,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"] @@ -646,15 +646,18 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, 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 diff --git a/tests/test_models.py b/tests/test_models.py index 26df341f..73e6d67c 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -854,3 +854,35 @@ class Model(BaseModel): 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) From cb2352da6b93465618458cf0d12a7408494ff3d5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 17:58:10 +0000 Subject: [PATCH 076/320] fix(ci): ensure pip is always available (#88) --- bin/publish-pypi | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/publish-pypi b/bin/publish-pypi index 05bfccbb..ebebf916 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -5,5 +5,6 @@ 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 ensurepip "$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1' rye publish --yes --token=$PYPI_TOKEN From e1383ac4afec1db3f70aba80b07401bc64ab4bdd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:05:48 +0000 Subject: [PATCH 077/320] fix(ci): remove publishing patch (#89) --- bin/publish-pypi | 4 ---- pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/bin/publish-pypi b/bin/publish-pypi index ebebf916..826054e9 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -3,8 +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 ensurepip -"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1' rye publish --yes --token=$PYPI_TOKEN diff --git a/pyproject.toml b/pyproject.toml index e2e84f14..ff7153aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,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] From 418bcd8240fc62d97937ecefdd9747400f4ee2da Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:29:08 +0000 Subject: [PATCH 078/320] feat(api): api update (#90) --- src/codex/resources/projects/access_keys.py | 31 +++++++ src/codex/resources/projects/entries.py | 91 +++++++++++++++++++ .../projects/access_key_create_params.py | 8 ++ .../projects/entry_add_question_params.py | 12 ++- .../types/projects/entry_create_params.py | 12 ++- .../types/projects/entry_query_params.py | 12 ++- .../projects/test_access_keys.py | 8 ++ tests/api_resources/projects/test_entries.py | 60 ++++++++++++ 8 files changed, 231 insertions(+), 3 deletions(-) diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index d375dbca..61987399 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -10,6 +10,7 @@ from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven from ..._utils import ( maybe_transform, + strip_not_given, async_maybe_transform, ) from ..._compat import cached_property @@ -56,6 +57,10 @@ def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -77,6 +82,17 @@ def create( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return self._post( f"/api/projects/{project_id}/access_keys/", body=maybe_transform( @@ -330,6 +346,10 @@ async def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -351,6 +371,17 @@ async def create( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return await self._post( f"/api/projects/{project_id}/access_keys/", body=await async_maybe_transform( diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index e2c16547..f5eebad6 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -10,6 +10,7 @@ from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven from ..._utils import ( maybe_transform, + strip_not_given, async_maybe_transform, ) from ..._compat import cached_property @@ -61,6 +62,10 @@ def create( question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -84,6 +89,17 @@ def create( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return self._post( f"/api/projects/{project_id}/entries/", body=maybe_transform( @@ -283,6 +299,10 @@ def add_question( project_id: str, *, question: str, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -307,6 +327,17 @@ def add_question( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return self._post( f"/api/projects/{project_id}/entries/add_question", body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), @@ -321,6 +352,10 @@ def query( project_id: str, *, question: str, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -349,6 +384,17 @@ def query( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return self._post( f"/api/projects/{project_id}/entries/query", body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams), @@ -386,6 +432,10 @@ async def create( question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -409,6 +459,17 @@ async def create( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return await self._post( f"/api/projects/{project_id}/entries/", body=await async_maybe_transform( @@ -608,6 +669,10 @@ async def add_question( project_id: str, *, question: str, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -632,6 +697,17 @@ async def add_question( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return await self._post( f"/api/projects/{project_id}/entries/add_question", body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), @@ -646,6 +722,10 @@ async def query( project_id: str, *, question: str, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, # 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, @@ -674,6 +754,17 @@ async def query( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } return await self._post( f"/api/projects/{project_id}/entries/query", body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams), diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py index 1cbc202f..cf5f00fb 100644 --- a/src/codex/types/projects/access_key_create_params.py +++ b/src/codex/types/projects/access_key_create_params.py @@ -17,3 +17,11 @@ class AccessKeyCreateParams(TypedDict, total=False): description: Optional[str] expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] + + x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] + + x_source: Annotated[str, PropertyInfo(alias="x-source")] + + x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] diff --git a/src/codex/types/projects/entry_add_question_params.py b/src/codex/types/projects/entry_add_question_params.py index e2d009b4..b310f96b 100644 --- a/src/codex/types/projects/entry_add_question_params.py +++ b/src/codex/types/projects/entry_add_question_params.py @@ -2,10 +2,20 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo __all__ = ["EntryAddQuestionParams"] class EntryAddQuestionParams(TypedDict, total=False): question: Required[str] + + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] + + x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] + + x_source: Annotated[str, PropertyInfo(alias="x-source")] + + x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py index daf30897..0022942f 100644 --- a/src/codex/types/projects/entry_create_params.py +++ b/src/codex/types/projects/entry_create_params.py @@ -3,7 +3,9 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Required, TypedDict +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo __all__ = ["EntryCreateParams"] @@ -14,3 +16,11 @@ class EntryCreateParams(TypedDict, total=False): answer: Optional[str] draft_answer: Optional[str] + + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] + + x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] + + x_source: Annotated[str, PropertyInfo(alias="x-source")] + + x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index b6fbc437..bc0e317c 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -2,10 +2,20 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo __all__ = ["EntryQueryParams"] class EntryQueryParams(TypedDict, total=False): question: Required[str] + + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] + + x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] + + x_source: Annotated[str, PropertyInfo(alias="x-source")] + + x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index 240c31e1..ad4ee5e4 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -39,6 +39,10 @@ def test_method_create_with_all_params(self, client: Codex) -> None: name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) @@ -395,6 +399,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index fbbf6f25..8a18dc01 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -37,6 +37,10 @@ def test_method_create_with_all_params(self, client: Codex) -> None: question="question", answer="answer", draft_answer="draft_answer", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", ) assert_matches_type(Entry, entry, path=["response"]) @@ -312,6 +316,19 @@ def test_method_add_question(self, client: Codex) -> None: ) assert_matches_type(Entry, entry, path=["response"]) + @pytest.mark.skip() + @parametrize + def test_method_add_question_with_all_params(self, client: Codex) -> None: + entry = client.projects.entries.add_question( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(Entry, entry, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_add_question(self, client: Codex) -> None: @@ -358,6 +375,19 @@ def test_method_query(self, client: Codex) -> None: ) assert_matches_type(Optional[Entry], entry, path=["response"]) + @pytest.mark.skip() + @parametrize + def test_method_query_with_all_params(self, client: Codex) -> None: + entry = client.projects.entries.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(Optional[Entry], entry, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_query(self, client: Codex) -> None: @@ -416,6 +446,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> question="question", answer="answer", draft_answer="draft_answer", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", ) assert_matches_type(Entry, entry, path=["response"]) @@ -691,6 +725,19 @@ async def test_method_add_question(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Entry, entry, path=["response"]) + @pytest.mark.skip() + @parametrize + async def test_method_add_question_with_all_params(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.add_question( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(Entry, entry, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: @@ -737,6 +784,19 @@ async def test_method_query(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Optional[Entry], entry, path=["response"]) + @pytest.mark.skip() + @parametrize + async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(Optional[Entry], entry, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_query(self, async_client: AsyncCodex) -> None: From d021150a95529d5d47bea715a7c4803b56c4fbf5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 05:25:22 +0000 Subject: [PATCH 079/320] chore: fix typos (#91) --- src/codex/_models.py | 2 +- src/codex/_utils/_transform.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index b51a1bf5..34935716 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -681,7 +681,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 diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index 18afd9d8..7ac2e17f 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -126,7 +126,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: From 2a180776687fbc3969cada6fc070a91e5046c9e5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 05:26:03 +0000 Subject: [PATCH 080/320] codegen metadata --- .stats.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.stats.yml b/.stats.yml index 7982133f..d526653e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1,3 @@ configured_endpoints: 37 +openapi_spec_hash: c2648fabb12e6096d5f7a3ab78300d66 +config_hash: 0529917e0e61d8cd6366481b77eff77e From 429c38b75d1e952ce93bd48c53966989df2245db Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 21:28:44 +0000 Subject: [PATCH 081/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d526653e..893f8fab 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 37 -openapi_spec_hash: c2648fabb12e6096d5f7a3ab78300d66 +openapi_spec_hash: 6993cffe14e021af26bf142f5b557263 config_hash: 0529917e0e61d8cd6366481b77eff77e From 7c346a8bfb0b804796e36ba9d3e252510dae92e2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:11:49 +0000 Subject: [PATCH 082/320] feat(api): updates from question grouping (#93) --- .stats.yml | 6 +- README.md | 28 +- api.md | 20 +- src/codex/pagination.py | 62 ++++ src/codex/resources/health.py | 50 --- src/codex/resources/projects/__init__.py | 14 + src/codex/resources/projects/clusters.py | 296 +++++++++++++++++ src/codex/resources/projects/entries.py | 299 +----------------- src/codex/resources/projects/projects.py | 32 ++ src/codex/types/projects/__init__.py | 6 +- ..._list_params.py => cluster_list_params.py} | 10 +- .../types/projects/cluster_list_response.py | 36 +++ .../cluster_list_variants_response.py | 14 + src/codex/types/projects/entry.py | 8 +- .../projects/entry_add_question_params.py | 21 -- .../types/projects/entry_query_response.py | 23 ++ .../types/projects/entry_update_params.py | 2 - tests/api_resources/projects/test_clusters.py | 241 ++++++++++++++ tests/api_resources/projects/test_entries.py | 257 +-------------- tests/api_resources/test_health.py | 56 ---- 20 files changed, 785 insertions(+), 696 deletions(-) create mode 100644 src/codex/resources/projects/clusters.py rename src/codex/types/projects/{entry_list_params.py => cluster_list_params.py} (66%) create mode 100644 src/codex/types/projects/cluster_list_response.py create mode 100644 src/codex/types/projects/cluster_list_variants_response.py delete mode 100644 src/codex/types/projects/entry_add_question_params.py create mode 100644 src/codex/types/projects/entry_query_response.py create mode 100644 tests/api_resources/projects/test_clusters.py diff --git a/.stats.yml b/.stats.yml index 893f8fab..d2d3f6a5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 37 -openapi_spec_hash: 6993cffe14e021af26bf142f5b557263 -config_hash: 0529917e0e61d8cd6366481b77eff77e +configured_endpoints: 36 +openapi_spec_hash: 4e7cb2cd6132c29f60a87a958f617a41 +config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/README.md b/README.md index 95452e72..73c8c169 100644 --- a/README.md +++ b/README.md @@ -87,14 +87,14 @@ from codex import Codex client = Codex() -all_entries = [] +all_clusters = [] # Automatically fetches more pages as needed. -for entry in client.projects.entries.list( +for cluster in client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ): - # Do something with entry here - all_entries.append(entry) -print(all_entries) + # Do something with cluster here + all_clusters.append(cluster) +print(all_clusters) ``` Or, asynchronously: @@ -107,13 +107,13 @@ client = AsyncCodex() async def main() -> None: - all_entries = [] + all_clusters = [] # Iterate through items across all pages, issuing requests as needed. - async for entry in client.projects.entries.list( + async for cluster in client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ): - all_entries.append(entry) - print(all_entries) + all_clusters.append(cluster) + print(all_clusters) asyncio.run(main()) @@ -122,13 +122,13 @@ asyncio.run(main()) Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: ```python -first_page = await client.projects.entries.list( +first_page = await client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) if first_page.has_next_page(): print(f"will fetch next page using these details: {first_page.next_page_info()}") next_page = await first_page.get_next_page() - print(f"number of items we just fetched: {len(next_page.entries)}") + print(f"number of items we just fetched: {len(next_page.clusters)}") # Remove `await` for non-async usage. ``` @@ -136,11 +136,11 @@ if first_page.has_next_page(): Or just work directly with the returned data: ```python -first_page = await client.projects.entries.list( +first_page = await client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) -for entry in first_page.entries: - print(entry.id) +for cluster in first_page.clusters: + print(cluster.id) # Remove `await` for non-async usage. ``` diff --git a/api.md b/api.md index 5e258334..b241c15c 100644 --- a/api.md +++ b/api.md @@ -10,7 +10,6 @@ Methods: - client.health.check() -> HealthCheckResponse - client.health.db() -> HealthCheckResponse -- client.health.weaviate() -> HealthCheckResponse # Organizations @@ -175,7 +174,7 @@ Methods: Types: ```python -from codex.types.projects import Entry +from codex.types.projects import Entry, EntryQueryResponse ``` Methods: @@ -183,10 +182,21 @@ Methods: - client.projects.entries.create(project_id, \*\*params) -> Entry - client.projects.entries.retrieve(entry_id, \*, project_id) -> Entry - client.projects.entries.update(entry_id, \*, project_id, \*\*params) -> Entry -- client.projects.entries.list(project_id, \*\*params) -> SyncOffsetPageEntries[Entry] - client.projects.entries.delete(entry_id, \*, project_id) -> None -- client.projects.entries.add_question(project_id, \*\*params) -> Entry -- client.projects.entries.query(project_id, \*\*params) -> Optional[Entry] +- client.projects.entries.query(project_id, \*\*params) -> EntryQueryResponse + +## Clusters + +Types: + +```python +from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse +``` + +Methods: + +- client.projects.clusters.list(project_id, \*\*params) -> SyncOffsetPageClusters[ClusterListResponse] +- client.projects.clusters.list_variants(representative_entry_id, \*, project_id) -> ClusterListVariantsResponse # Tlm diff --git a/src/codex/pagination.py b/src/codex/pagination.py index 48ed5018..1f590df5 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -12,6 +12,8 @@ __all__ = [ "SyncMyOffsetPageTopLevelArray", "AsyncMyOffsetPageTopLevelArray", + "SyncOffsetPageClusters", + "AsyncOffsetPageClusters", "SyncOffsetPageEntries", "AsyncOffsetPageEntries", ] @@ -83,6 +85,66 @@ def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseM ) +class SyncOffsetPageClusters(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + clusters: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + clusters = self.clusters + if not clusters: + return [] + return clusters + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncOffsetPageClusters(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + clusters: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + clusters = self.clusters + if not clusters: + return [] + return clusters + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + class SyncOffsetPageEntries(BaseSyncPage[_T], BasePage[_T], Generic[_T]): entries: List[_T] total_count: Optional[int] = None diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py index 6a777c63..d74d23a5 100644 --- a/src/codex/resources/health.py +++ b/src/codex/resources/health.py @@ -77,25 +77,6 @@ def db( cast_to=HealthCheckResponse, ) - def weaviate( - self, - *, - # 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, - ) -> HealthCheckResponse: - """Check the weaviate connection.""" - return self._get( - "/api/health/weaviate", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=HealthCheckResponse, - ) - class AsyncHealthResource(AsyncAPIResource): @cached_property @@ -155,25 +136,6 @@ async def db( cast_to=HealthCheckResponse, ) - async def weaviate( - self, - *, - # 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, - ) -> HealthCheckResponse: - """Check the weaviate connection.""" - return await self._get( - "/api/health/weaviate", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=HealthCheckResponse, - ) - class HealthResourceWithRawResponse: def __init__(self, health: HealthResource) -> None: @@ -185,9 +147,6 @@ def __init__(self, health: HealthResource) -> None: self.db = to_raw_response_wrapper( health.db, ) - self.weaviate = to_raw_response_wrapper( - health.weaviate, - ) class AsyncHealthResourceWithRawResponse: @@ -200,9 +159,6 @@ def __init__(self, health: AsyncHealthResource) -> None: self.db = async_to_raw_response_wrapper( health.db, ) - self.weaviate = async_to_raw_response_wrapper( - health.weaviate, - ) class HealthResourceWithStreamingResponse: @@ -215,9 +171,6 @@ def __init__(self, health: HealthResource) -> None: self.db = to_streamed_response_wrapper( health.db, ) - self.weaviate = to_streamed_response_wrapper( - health.weaviate, - ) class AsyncHealthResourceWithStreamingResponse: @@ -230,6 +183,3 @@ def __init__(self, health: AsyncHealthResource) -> None: self.db = async_to_streamed_response_wrapper( health.db, ) - self.weaviate = async_to_streamed_response_wrapper( - health.weaviate, - ) diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py index 01599858..2c0595d2 100644 --- a/src/codex/resources/projects/__init__.py +++ b/src/codex/resources/projects/__init__.py @@ -8,6 +8,14 @@ EntriesResourceWithStreamingResponse, AsyncEntriesResourceWithStreamingResponse, ) +from .clusters import ( + ClustersResource, + AsyncClustersResource, + ClustersResourceWithRawResponse, + AsyncClustersResourceWithRawResponse, + ClustersResourceWithStreamingResponse, + AsyncClustersResourceWithStreamingResponse, +) from .projects import ( ProjectsResource, AsyncProjectsResource, @@ -38,6 +46,12 @@ "AsyncEntriesResourceWithRawResponse", "EntriesResourceWithStreamingResponse", "AsyncEntriesResourceWithStreamingResponse", + "ClustersResource", + "AsyncClustersResource", + "ClustersResourceWithRawResponse", + "AsyncClustersResourceWithRawResponse", + "ClustersResourceWithStreamingResponse", + "AsyncClustersResourceWithStreamingResponse", "ProjectsResource", "AsyncProjectsResource", "ProjectsResourceWithRawResponse", diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py new file mode 100644 index 00000000..54b6c4c3 --- /dev/null +++ b/src/codex/resources/projects/clusters.py @@ -0,0 +1,296 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._utils import maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters +from ..._base_client import AsyncPaginator, make_request_options +from ...types.projects import cluster_list_params +from ...types.projects.cluster_list_response import ClusterListResponse +from ...types.projects.cluster_list_variants_response import ClusterListVariantsResponse + +__all__ = ["ClustersResource", "AsyncClustersResource"] + + +class ClustersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ClustersResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return ClustersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ClustersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return ClustersResourceWithStreamingResponse(self) + + def list( + self, + project_id: str, + *, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN, + states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, + # 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, + ) -> SyncOffsetPageClusters[ClusterListResponse]: + """ + List knowledge entries for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + f"/api/projects/{project_id}/entries/clusters", + page=SyncOffsetPageClusters[ClusterListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "states": states, + }, + cluster_list_params.ClusterListParams, + ), + ), + model=ClusterListResponse, + ) + + def list_variants( + self, + representative_entry_id: str, + *, + project_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, + ) -> ClusterListVariantsResponse: + """ + Get Cluster Variants Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not representative_entry_id: + raise ValueError( + f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}" + ) + return self._get( + f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ClusterListVariantsResponse, + ) + + +class AsyncClustersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncClustersResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncClustersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncClustersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncClustersResourceWithStreamingResponse(self) + + def list( + self, + project_id: str, + *, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN, + states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[ClusterListResponse, AsyncOffsetPageClusters[ClusterListResponse]]: + """ + List knowledge entries for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + f"/api/projects/{project_id}/entries/clusters", + page=AsyncOffsetPageClusters[ClusterListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "states": states, + }, + cluster_list_params.ClusterListParams, + ), + ), + model=ClusterListResponse, + ) + + async def list_variants( + self, + representative_entry_id: str, + *, + project_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, + ) -> ClusterListVariantsResponse: + """ + Get Cluster Variants Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not representative_entry_id: + raise ValueError( + f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}" + ) + return await self._get( + f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ClusterListVariantsResponse, + ) + + +class ClustersResourceWithRawResponse: + def __init__(self, clusters: ClustersResource) -> None: + self._clusters = clusters + + self.list = to_raw_response_wrapper( + clusters.list, + ) + self.list_variants = to_raw_response_wrapper( + clusters.list_variants, + ) + + +class AsyncClustersResourceWithRawResponse: + def __init__(self, clusters: AsyncClustersResource) -> None: + self._clusters = clusters + + self.list = async_to_raw_response_wrapper( + clusters.list, + ) + self.list_variants = async_to_raw_response_wrapper( + clusters.list_variants, + ) + + +class ClustersResourceWithStreamingResponse: + def __init__(self, clusters: ClustersResource) -> None: + self._clusters = clusters + + self.list = to_streamed_response_wrapper( + clusters.list, + ) + self.list_variants = to_streamed_response_wrapper( + clusters.list_variants, + ) + + +class AsyncClustersResourceWithStreamingResponse: + def __init__(self, clusters: AsyncClustersResource) -> None: + self._clusters = clusters + + self.list = async_to_streamed_response_wrapper( + clusters.list, + ) + self.list_variants = async_to_streamed_response_wrapper( + clusters.list_variants, + ) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index f5eebad6..8be96b06 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import List, Optional -from typing_extensions import Literal +from typing import Optional import httpx @@ -21,16 +20,10 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ...pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries -from ..._base_client import AsyncPaginator, make_request_options -from ...types.projects import ( - entry_list_params, - entry_query_params, - entry_create_params, - entry_update_params, - entry_add_question_params, -) +from ..._base_client import make_request_options +from ...types.projects import entry_query_params, entry_create_params, entry_update_params from ...types.projects.entry import Entry +from ...types.projects.entry_query_response import EntryQueryResponse __all__ = ["EntriesResource", "AsyncEntriesResource"] @@ -74,9 +67,7 @@ def create( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Entry: """ - Create a knowledge entry for a project. - - Raises: HTTPException: If an existing entry is found with the same question. + Create a new knowledge entry for a project. Args: extra_headers: Send extra headers @@ -159,7 +150,6 @@ def update( project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - frequency_count: Optional[int] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -190,7 +180,6 @@ def update( { "answer": answer, "draft_answer": draft_answer, - "frequency_count": frequency_count, "question": question, }, entry_update_params.EntryUpdateParams, @@ -201,62 +190,6 @@ def update( cast_to=Entry, ) - def list( - self, - project_id: str, - *, - answered_only: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, - states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, - unanswered_only: bool | NotGiven = NOT_GIVEN, - # 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, - ) -> SyncOffsetPageEntries[Entry]: - """ - List knowledge entries for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._get_api_list( - f"/api/projects/{project_id}/entries/", - page=SyncOffsetPageEntries[Entry], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "answered_only": answered_only, - "limit": limit, - "offset": offset, - "order": order, - "sort": sort, - "states": states, - "unanswered_only": unanswered_only, - }, - entry_list_params.EntryListParams, - ), - ), - model=Entry, - ) - def delete( self, entry_id: str, @@ -294,59 +227,6 @@ def delete( cast_to=NoneType, ) - def add_question( - self, - project_id: str, - *, - question: str, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Add a question to a project. - - Returns: 201 Created if a new question was added 200 OK if the question already - existed - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - extra_headers = { - **strip_not_given( - { - "x-client-library-version": x_client_library_version, - "x-integration-type": x_integration_type, - "x-source": x_source, - "x-stainless-package-version": x_stainless_package_version, - } - ), - **(extra_headers or {}), - } - return self._post( - f"/api/projects/{project_id}/entries/add_question", - body=maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - def query( self, project_id: str, @@ -362,16 +242,9 @@ def query( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Optional[Entry]: - """Query knowledge for a project. - - Also increments the frequency_count for the - matching entry if found. - - Returns the matching entry if found and answered, otherwise returns None. This - allows the client to distinguish between: (1) no matching question found - (returns None), and (2) matching question found but not yet answered (returns - Entry with answer=None). + ) -> EntryQueryResponse: + """ + Query Entries Route Args: extra_headers: Send extra headers @@ -401,7 +274,7 @@ def query( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Entry, + cast_to=EntryQueryResponse, ) @@ -444,9 +317,7 @@ async def create( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Entry: """ - Create a knowledge entry for a project. - - Raises: HTTPException: If an existing entry is found with the same question. + Create a new knowledge entry for a project. Args: extra_headers: Send extra headers @@ -529,7 +400,6 @@ async def update( project_id: str, answer: Optional[str] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - frequency_count: Optional[int] | NotGiven = NOT_GIVEN, question: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -560,7 +430,6 @@ async def update( { "answer": answer, "draft_answer": draft_answer, - "frequency_count": frequency_count, "question": question, }, entry_update_params.EntryUpdateParams, @@ -571,62 +440,6 @@ async def update( cast_to=Entry, ) - def list( - self, - project_id: str, - *, - answered_only: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "answered_at"] | NotGiven = NOT_GIVEN, - states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, - unanswered_only: bool | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[Entry, AsyncOffsetPageEntries[Entry]]: - """ - List knowledge entries for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._get_api_list( - f"/api/projects/{project_id}/entries/", - page=AsyncOffsetPageEntries[Entry], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "answered_only": answered_only, - "limit": limit, - "offset": offset, - "order": order, - "sort": sort, - "states": states, - "unanswered_only": unanswered_only, - }, - entry_list_params.EntryListParams, - ), - ), - model=Entry, - ) - async def delete( self, entry_id: str, @@ -664,59 +477,6 @@ async def delete( cast_to=NoneType, ) - async def add_question( - self, - project_id: str, - *, - question: str, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Add a question to a project. - - Returns: 201 Created if a new question was added 200 OK if the question already - existed - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - extra_headers = { - **strip_not_given( - { - "x-client-library-version": x_client_library_version, - "x-integration-type": x_integration_type, - "x-source": x_source, - "x-stainless-package-version": x_stainless_package_version, - } - ), - **(extra_headers or {}), - } - return await self._post( - f"/api/projects/{project_id}/entries/add_question", - body=await async_maybe_transform({"question": question}, entry_add_question_params.EntryAddQuestionParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - async def query( self, project_id: str, @@ -732,16 +492,9 @@ async def query( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Optional[Entry]: - """Query knowledge for a project. - - Also increments the frequency_count for the - matching entry if found. - - Returns the matching entry if found and answered, otherwise returns None. This - allows the client to distinguish between: (1) no matching question found - (returns None), and (2) matching question found but not yet answered (returns - Entry with answer=None). + ) -> EntryQueryResponse: + """ + Query Entries Route Args: extra_headers: Send extra headers @@ -771,7 +524,7 @@ async def query( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Entry, + cast_to=EntryQueryResponse, ) @@ -788,15 +541,9 @@ def __init__(self, entries: EntriesResource) -> None: self.update = to_raw_response_wrapper( entries.update, ) - self.list = to_raw_response_wrapper( - entries.list, - ) self.delete = to_raw_response_wrapper( entries.delete, ) - self.add_question = to_raw_response_wrapper( - entries.add_question, - ) self.query = to_raw_response_wrapper( entries.query, ) @@ -815,15 +562,9 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.update = async_to_raw_response_wrapper( entries.update, ) - self.list = async_to_raw_response_wrapper( - entries.list, - ) self.delete = async_to_raw_response_wrapper( entries.delete, ) - self.add_question = async_to_raw_response_wrapper( - entries.add_question, - ) self.query = async_to_raw_response_wrapper( entries.query, ) @@ -842,15 +583,9 @@ def __init__(self, entries: EntriesResource) -> None: self.update = to_streamed_response_wrapper( entries.update, ) - self.list = to_streamed_response_wrapper( - entries.list, - ) self.delete = to_streamed_response_wrapper( entries.delete, ) - self.add_question = to_streamed_response_wrapper( - entries.add_question, - ) self.query = to_streamed_response_wrapper( entries.query, ) @@ -869,15 +604,9 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.update = async_to_streamed_response_wrapper( entries.update, ) - self.list = async_to_streamed_response_wrapper( - entries.list, - ) self.delete = async_to_streamed_response_wrapper( entries.delete, ) - self.add_question = async_to_streamed_response_wrapper( - entries.add_question, - ) self.query = async_to_streamed_response_wrapper( entries.query, ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 38d7b036..8a7ff1b3 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -21,6 +21,14 @@ maybe_transform, async_maybe_transform, ) +from .clusters import ( + ClustersResource, + AsyncClustersResource, + ClustersResourceWithRawResponse, + AsyncClustersResourceWithRawResponse, + ClustersResourceWithStreamingResponse, + AsyncClustersResourceWithStreamingResponse, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -53,6 +61,10 @@ def access_keys(self) -> AccessKeysResource: def entries(self) -> EntriesResource: return EntriesResource(self._client) + @cached_property + def clusters(self) -> ClustersResource: + return ClustersResource(self._client) + @cached_property def with_raw_response(self) -> ProjectsResourceWithRawResponse: """ @@ -321,6 +333,10 @@ def access_keys(self) -> AsyncAccessKeysResource: def entries(self) -> AsyncEntriesResource: return AsyncEntriesResource(self._client) + @cached_property + def clusters(self) -> AsyncClustersResource: + return AsyncClustersResource(self._client) + @cached_property def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: """ @@ -611,6 +627,10 @@ def access_keys(self) -> AccessKeysResourceWithRawResponse: def entries(self) -> EntriesResourceWithRawResponse: return EntriesResourceWithRawResponse(self._projects.entries) + @cached_property + def clusters(self) -> ClustersResourceWithRawResponse: + return ClustersResourceWithRawResponse(self._projects.clusters) + class AsyncProjectsResourceWithRawResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -643,6 +663,10 @@ def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: def entries(self) -> AsyncEntriesResourceWithRawResponse: return AsyncEntriesResourceWithRawResponse(self._projects.entries) + @cached_property + def clusters(self) -> AsyncClustersResourceWithRawResponse: + return AsyncClustersResourceWithRawResponse(self._projects.clusters) + class ProjectsResourceWithStreamingResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -675,6 +699,10 @@ def access_keys(self) -> AccessKeysResourceWithStreamingResponse: def entries(self) -> EntriesResourceWithStreamingResponse: return EntriesResourceWithStreamingResponse(self._projects.entries) + @cached_property + def clusters(self) -> ClustersResourceWithStreamingResponse: + return ClustersResourceWithStreamingResponse(self._projects.clusters) + class AsyncProjectsResourceWithStreamingResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -706,3 +734,7 @@ def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: @cached_property def entries(self) -> AsyncEntriesResourceWithStreamingResponse: return AsyncEntriesResourceWithStreamingResponse(self._projects.entries) + + @cached_property + def clusters(self) -> AsyncClustersResourceWithStreamingResponse: + return AsyncClustersResourceWithStreamingResponse(self._projects.clusters) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 44c304f5..2733406c 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -4,14 +4,16 @@ from .entry import Entry as Entry from .access_key_schema import AccessKeySchema as AccessKeySchema -from .entry_list_params import EntryListParams as EntryListParams from .entry_query_params import EntryQueryParams as EntryQueryParams +from .cluster_list_params import ClusterListParams as ClusterListParams from .entry_create_params import EntryCreateParams as EntryCreateParams from .entry_update_params import EntryUpdateParams as EntryUpdateParams +from .entry_query_response import EntryQueryResponse as EntryQueryResponse +from .cluster_list_response import ClusterListResponse as ClusterListResponse from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams -from .entry_add_question_params import EntryAddQuestionParams as EntryAddQuestionParams +from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse from .access_key_retrieve_project_id_response import ( AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse, ) diff --git a/src/codex/types/projects/entry_list_params.py b/src/codex/types/projects/cluster_list_params.py similarity index 66% rename from src/codex/types/projects/entry_list_params.py rename to src/codex/types/projects/cluster_list_params.py index 605ea482..438b4812 100644 --- a/src/codex/types/projects/entry_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -5,20 +5,16 @@ from typing import List from typing_extensions import Literal, TypedDict -__all__ = ["EntryListParams"] +__all__ = ["ClusterListParams"] -class EntryListParams(TypedDict, total=False): - answered_only: bool - +class ClusterListParams(TypedDict, total=False): limit: int offset: int order: Literal["asc", "desc"] - sort: Literal["created_at", "answered_at"] + sort: Literal["created_at", "answered_at", "cluster_frequency_count"] states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] - - unanswered_only: bool diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py new file mode 100644 index 00000000..7b4ef3ad --- /dev/null +++ b/src/codex/types/projects/cluster_list_response.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ClusterListResponse"] + + +class ClusterListResponse(BaseModel): + id: str + + cluster_frequency_count: int + + created_at: datetime + + project_id: str + + question: str + + state: Literal["unanswered", "draft", "published", "published_with_draft"] + + answer: Optional[str] = None + + answered_at: Optional[datetime] = None + + draft_answer: Optional[str] = None + + draft_answer_last_edited: Optional[datetime] = None + + frequency_count: Optional[int] = None + """number of times the entry matched for a /query request""" + + representative_entry_id: Optional[str] = None diff --git a/src/codex/types/projects/cluster_list_variants_response.py b/src/codex/types/projects/cluster_list_variants_response.py new file mode 100644 index 00000000..aa359058 --- /dev/null +++ b/src/codex/types/projects/cluster_list_variants_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .entry import Entry +from ..._models import BaseModel + +__all__ = ["ClusterListVariantsResponse"] + + +class ClusterListVariantsResponse(BaseModel): + entries: List[Entry] + + total_count: int diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index 7a2c4502..442eddd8 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -10,14 +10,16 @@ class Entry(BaseModel): + id: str + created_at: datetime + project_id: str + question: str state: Literal["unanswered", "draft", "published", "published_with_draft"] - id: Optional[str] = None - answer: Optional[str] = None answered_at: Optional[datetime] = None @@ -25,5 +27,3 @@ class Entry(BaseModel): draft_answer: Optional[str] = None draft_answer_last_edited: Optional[datetime] = None - - frequency_count: Optional[int] = None diff --git a/src/codex/types/projects/entry_add_question_params.py b/src/codex/types/projects/entry_add_question_params.py deleted file mode 100644 index b310f96b..00000000 --- a/src/codex/types/projects/entry_add_question_params.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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__ = ["EntryAddQuestionParams"] - - -class EntryAddQuestionParams(TypedDict, total=False): - question: Required[str] - - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] - - x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] - - x_source: Annotated[str, PropertyInfo(alias="x-source")] - - x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py new file mode 100644 index 00000000..ad106d7a --- /dev/null +++ b/src/codex/types/projects/entry_query_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["EntryQueryResponse", "Entry"] + + +class Entry(BaseModel): + id: str + + question: str + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + +class EntryQueryResponse(BaseModel): + entry: Entry + + answer: Optional[str] = None diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py index 05a6332d..aac256f9 100644 --- a/src/codex/types/projects/entry_update_params.py +++ b/src/codex/types/projects/entry_update_params.py @@ -15,6 +15,4 @@ class EntryUpdateParams(TypedDict, total=False): draft_answer: Optional[str] - frequency_count: Optional[int] - question: Optional[str] diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py new file mode 100644 index 00000000..5a464970 --- /dev/null +++ b/tests/api_resources/projects/test_clusters.py @@ -0,0 +1,241 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex.pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters +from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestClusters: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_list(self, client: Codex) -> None: + cluster = client.projects.clusters.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + cluster = client.projects.clusters.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + limit=1, + offset=0, + order="asc", + sort="created_at", + states=["unanswered"], + ) + assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.clusters.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + cluster = response.parse() + assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.clusters.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + cluster = response.parse() + assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.clusters.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + def test_method_list_variants(self, client: Codex) -> None: + cluster = client.projects.clusters.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list_variants(self, client: Codex) -> None: + response = client.projects.clusters.with_raw_response.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + cluster = response.parse() + assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list_variants(self, client: Codex) -> None: + with client.projects.clusters.with_streaming_response.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + cluster = response.parse() + assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list_variants(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.clusters.with_raw_response.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''" + ): + client.projects.clusters.with_raw_response.list_variants( + representative_entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncClusters: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + cluster = await async_client.projects.clusters.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + cluster = await async_client.projects.clusters.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + limit=1, + offset=0, + order="asc", + sort="created_at", + states=["unanswered"], + ) + assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.clusters.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + cluster = await response.parse() + assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.clusters.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + cluster = await response.parse() + assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.clusters.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list_variants(self, async_client: AsyncCodex) -> None: + cluster = await async_client.projects.clusters.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list_variants(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.clusters.with_raw_response.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + cluster = await response.parse() + assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list_variants(self, async_client: AsyncCodex) -> None: + async with async_client.projects.clusters.with_streaming_response.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + cluster = await response.parse() + assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list_variants(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.clusters.with_raw_response.list_variants( + representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''" + ): + await async_client.projects.clusters.with_raw_response.list_variants( + representative_entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 8a18dc01..e8965e4a 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -3,16 +3,13 @@ from __future__ import annotations import os -from typing import Any, Optional, cast +from typing import Any, cast import pytest from codex import Codex, AsyncCodex from tests.utils import assert_matches_type -from codex.pagination import SyncOffsetPageEntries, AsyncOffsetPageEntries -from codex.types.projects import ( - Entry, -) +from codex.types.projects import Entry, EntryQueryResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -150,7 +147,6 @@ def test_method_update_with_all_params(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", draft_answer="draft_answer", - frequency_count=0, question="question", ) assert_matches_type(Entry, entry, path=["response"]) @@ -198,63 +194,6 @@ def test_path_params_update(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() - @parametrize - def test_method_list(self, client: Codex) -> None: - entry = client.projects.entries.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_list_with_all_params(self, client: Codex) -> None: - entry = client.projects.entries.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answered_only=True, - limit=1, - offset=0, - order="asc", - sort="created_at", - states=["unanswered"], - unanswered_only=True, - ) - assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(SyncOffsetPageEntries[Entry], entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_list(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.list( - project_id="", - ) - @pytest.mark.skip() @parametrize def test_method_delete(self, client: Codex) -> None: @@ -307,65 +246,6 @@ def test_path_params_delete(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() - @parametrize - def test_method_add_question(self, client: Codex) -> None: - entry = client.projects.entries.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_add_question_with_all_params(self, client: Codex) -> None: - entry = client.projects.entries.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_add_question(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_add_question(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_add_question(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.add_question( - project_id="", - question="question", - ) - @pytest.mark.skip() @parametrize def test_method_query(self, client: Codex) -> None: @@ -373,7 +253,7 @@ def test_method_query(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -386,7 +266,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None: x_source="x-source", x_stainless_package_version="x-stainless-package-version", ) - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -399,7 +279,7 @@ def test_raw_response_query(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = response.parse() - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -412,7 +292,7 @@ def test_streaming_response_query(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = response.parse() - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) assert cast(Any, response.is_closed) is True @@ -559,7 +439,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", answer="answer", draft_answer="draft_answer", - frequency_count=0, question="question", ) assert_matches_type(Entry, entry, path=["response"]) @@ -607,63 +486,6 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() - @parametrize - async def test_method_list(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answered_only=True, - limit=1, - offset=0, - order="asc", - sort="created_at", - states=["unanswered"], - unanswered_only=True, - ) - assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(AsyncOffsetPageEntries[Entry], entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_list(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.list( - project_id="", - ) - @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: @@ -716,65 +538,6 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() - @parametrize - async def test_method_add_question(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_add_question_with_all_params(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_add_question(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_add_question(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.add_question( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_add_question(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.add_question( - project_id="", - question="question", - ) - @pytest.mark.skip() @parametrize async def test_method_query(self, async_client: AsyncCodex) -> None: @@ -782,7 +545,7 @@ async def test_method_query(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", ) - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -795,7 +558,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N x_source="x-source", x_stainless_package_version="x-stainless-package-version", ) - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -808,7 +571,7 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = await response.parse() - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize @@ -821,7 +584,7 @@ async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" entry = await response.parse() - assert_matches_type(Optional[Entry], entry, path=["response"]) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py index 09700120..ed942cd2 100644 --- a/tests/api_resources/test_health.py +++ b/tests/api_resources/test_health.py @@ -73,34 +73,6 @@ def test_streaming_response_db(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() - @parametrize - def test_method_weaviate(self, client: Codex) -> None: - health = client.health.weaviate() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_weaviate(self, client: Codex) -> None: - response = client.health.with_raw_response.weaviate() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - health = response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_weaviate(self, client: Codex) -> None: - with client.health.with_streaming_response.weaviate() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - health = response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - assert cast(Any, response.is_closed) is True - class TestAsyncHealth: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -160,31 +132,3 @@ async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: assert_matches_type(HealthCheckResponse, health, path=["response"]) assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_method_weaviate(self, async_client: AsyncCodex) -> None: - health = await async_client.health.weaviate() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_weaviate(self, async_client: AsyncCodex) -> None: - response = await async_client.health.with_raw_response.weaviate() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - health = await response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_weaviate(self, async_client: AsyncCodex) -> None: - async with async_client.health.with_streaming_response.weaviate() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - health = await response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - assert cast(Any, response.is_closed) is True From 7e259409666e8642c3e97cd8bede90b9749b9dae Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:16:36 +0000 Subject: [PATCH 083/320] chore(internal): version bump (#95) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fd0ccba9..000572ec 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.12" + ".": "0.1.0-alpha.13" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ff7153aa..a4cf5307 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.12" +version = "0.1.0-alpha.13" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index fe7cc739..8790f4ea 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.12" # x-release-please-version +__version__ = "0.1.0-alpha.13" # x-release-please-version From 208dfc4ebc22ec9d687928d12298144e2d2d30ff Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:29:04 +0000 Subject: [PATCH 084/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d2d3f6a5..86d74db8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: 4e7cb2cd6132c29f60a87a958f617a41 +openapi_spec_hash: d84beeafebbb22261946b77758a506a6 config_hash: adbedb6317fca6f566f54564cc341846 From 6035794a69860eec4301d8640e6e4d98b9cda6c3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:20:03 +0000 Subject: [PATCH 085/320] feat(api): api update (#98) --- .stats.yml | 2 +- src/codex/resources/projects/entries.py | 24 ++++++++++++++++--- .../types/projects/cluster_list_response.py | 4 +++- src/codex/types/projects/entry.py | 4 +++- .../types/projects/entry_create_params.py | 4 +++- .../types/projects/entry_query_params.py | 3 +++ .../types/projects/entry_query_response.py | 4 +++- tests/api_resources/projects/test_entries.py | 4 ++++ 8 files changed, 41 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index 86d74db8..c22765ce 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: d84beeafebbb22261946b77758a506a6 +openapi_spec_hash: 6a5cfa54c19b354978b1654c152b0431 config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 8be96b06..7df9f1f6 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Iterable, Optional import httpx @@ -54,6 +54,7 @@ def create( *, question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -97,6 +98,7 @@ def create( { "question": question, "answer": answer, + "client_query_metadata": client_query_metadata, "draft_answer": draft_answer, }, entry_create_params.EntryCreateParams, @@ -232,6 +234,7 @@ def query( project_id: str, *, question: str, + client_metadata: Optional[object] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -270,7 +273,13 @@ def query( } return self._post( f"/api/projects/{project_id}/entries/query", - body=maybe_transform({"question": question}, entry_query_params.EntryQueryParams), + body=maybe_transform( + { + "question": question, + "client_metadata": client_metadata, + }, + entry_query_params.EntryQueryParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -304,6 +313,7 @@ async def create( *, question: str, answer: Optional[str] | NotGiven = NOT_GIVEN, + client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -347,6 +357,7 @@ async def create( { "question": question, "answer": answer, + "client_query_metadata": client_query_metadata, "draft_answer": draft_answer, }, entry_create_params.EntryCreateParams, @@ -482,6 +493,7 @@ async def query( project_id: str, *, question: str, + client_metadata: Optional[object] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -520,7 +532,13 @@ async def query( } return await self._post( f"/api/projects/{project_id}/entries/query", - body=await async_maybe_transform({"question": question}, entry_query_params.EntryQueryParams), + body=await async_maybe_transform( + { + "question": question, + "client_metadata": client_metadata, + }, + entry_query_params.EntryQueryParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py index 7b4ef3ad..d13a5f98 100644 --- a/src/codex/types/projects/cluster_list_response.py +++ b/src/codex/types/projects/cluster_list_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime from typing_extensions import Literal @@ -26,6 +26,8 @@ class ClusterListResponse(BaseModel): answered_at: Optional[datetime] = None + client_query_metadata: Optional[List[object]] = None + draft_answer: Optional[str] = None draft_answer_last_edited: Optional[datetime] = None diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index 442eddd8..77e2ca33 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime from typing_extensions import Literal @@ -24,6 +24,8 @@ class Entry(BaseModel): answered_at: Optional[datetime] = None + client_query_metadata: Optional[List[object]] = None + draft_answer: Optional[str] = None draft_answer_last_edited: Optional[datetime] = None diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py index 0022942f..f06846bb 100644 --- a/src/codex/types/projects/entry_create_params.py +++ b/src/codex/types/projects/entry_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Iterable, Optional from typing_extensions import Required, Annotated, TypedDict from ..._utils import PropertyInfo @@ -15,6 +15,8 @@ class EntryCreateParams(TypedDict, total=False): answer: Optional[str] + client_query_metadata: Iterable[object] + draft_answer: Optional[str] x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index bc0e317c..50b5f268 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import Optional from typing_extensions import Required, Annotated, TypedDict from ..._utils import PropertyInfo @@ -12,6 +13,8 @@ class EntryQueryParams(TypedDict, total=False): question: Required[str] + client_metadata: Optional[object] + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py index ad106d7a..766e1e24 100644 --- a/src/codex/types/projects/entry_query_response.py +++ b/src/codex/types/projects/entry_query_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from ..._models import BaseModel @@ -14,6 +14,8 @@ class Entry(BaseModel): answer: Optional[str] = None + client_query_metadata: Optional[List[object]] = None + draft_answer: Optional[str] = None diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index e8965e4a..5fa5ed9a 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -33,6 +33,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", answer="answer", + client_query_metadata=[{}], draft_answer="draft_answer", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -261,6 +262,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None: entry = client.projects.entries.query( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", + client_metadata={}, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -325,6 +327,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", answer="answer", + client_query_metadata=[{}], draft_answer="draft_answer", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -553,6 +556,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N entry = await async_client.projects.entries.query( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", + client_metadata={}, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", From 6dc925a18d9efec16d0a922af510584053c212d0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:29:53 +0000 Subject: [PATCH 086/320] chore(internal): version bump (#99) --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 000572ec..b0699969 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.13" + ".": "0.1.0-alpha.14" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a4cf5307..ff15445f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.13" +version = "0.1.0-alpha.14" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 8790f4ea..15097538 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.13" # x-release-please-version +__version__ = "0.1.0-alpha.14" # x-release-please-version From b7c547ea3d057ac646ce9527a39e605327dd2bcb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:29:48 +0000 Subject: [PATCH 087/320] feat(api): api update (#102) --- .stats.yml | 2 +- src/codex/resources/projects/clusters.py | 6 +++--- src/codex/types/projects/cluster_list_params.py | 4 ++-- src/codex/types/projects/entry.py | 3 +++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index c22765ce..be1ee82d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: 6a5cfa54c19b354978b1654c152b0431 +openapi_spec_hash: 1850a850b7e27992c6e47190db9add88 config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index 54b6c4c3..2faed31e 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List +from typing import List, Optional from typing_extensions import Literal import httpx @@ -53,7 +53,7 @@ def list( limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # 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. @@ -164,7 +164,7 @@ def list( limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # 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. diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py index 438b4812..48893245 100644 --- a/src/codex/types/projects/cluster_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List +from typing import List, Optional from typing_extensions import Literal, TypedDict __all__ = ["ClusterListParams"] @@ -15,6 +15,6 @@ class ClusterListParams(TypedDict, total=False): order: Literal["asc", "desc"] - sort: Literal["created_at", "answered_at", "cluster_frequency_count"] + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index 77e2ca33..b8eeb0ec 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -29,3 +29,6 @@ class Entry(BaseModel): draft_answer: Optional[str] = None draft_answer_last_edited: Optional[datetime] = None + + frequency_count: Optional[int] = None + """number of times the entry matched for a /query request""" From c360da6924a128cbd98438252d1c1ad3922f9f6d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 19:58:07 +0000 Subject: [PATCH 088/320] chore(internal): remove trailing character (#103) --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index 0b0b783b..2d356fac 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1577,7 +1577,7 @@ def test_get_platform(self) -> None: import threading from codex._utils import asyncify - from codex._base_client import get_platform + from codex._base_client import get_platform async def test_main() -> None: result = await asyncify(get_platform)() From 4abd0f4ccf3461a47b4dfeece23f2c8e7fcdd5e6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 23:28:59 +0000 Subject: [PATCH 089/320] feat(api): api update (#104) --- .stats.yml | 2 +- README.md | 10 ++++- src/codex/resources/projects/entries.py | 16 +++++++- src/codex/types/project_create_params.py | 12 ++++++ src/codex/types/project_list_response.py | 12 ++++++ src/codex/types/project_return_schema.py | 12 ++++++ src/codex/types/project_update_params.py | 12 ++++++ .../types/projects/entry_query_params.py | 2 + tests/api_resources/projects/test_entries.py | 2 + tests/api_resources/test_projects.py | 40 +++++++++++++++++-- 10 files changed, 112 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index be1ee82d..69bb6fce 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: 1850a850b7e27992c6e47190db9add88 +openapi_spec_hash: 8ef89533cd58e3b2ceb53a877832f48b config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/README.md b/README.md index 73c8c169..c2f168fe 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,15 @@ from codex import Codex client = Codex() project_return_schema = client.projects.create( - config={"max_distance": 0}, + config={ + "clustering_use_llm_matching": True, + "llm_matching_model": "llm_matching_model", + "llm_matching_quality_preset": "llm_matching_quality_preset", + "lower_llm_match_distance_threshold": 0, + "max_distance": 0, + "query_use_llm_matching": True, + "upper_llm_match_distance_threshold": 0, + }, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 7df9f1f6..f0fa39cb 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -234,6 +234,7 @@ def query( project_id: str, *, question: str, + use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -281,7 +282,11 @@ def query( entry_query_params.EntryQueryParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams), ), cast_to=EntryQueryResponse, ) @@ -493,6 +498,7 @@ async def query( project_id: str, *, question: str, + use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -540,7 +546,13 @@ async def query( entry_query_params.EntryQueryParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams + ), ), cast_to=EntryQueryResponse, ) diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index 80882b21..ecdd194d 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -19,4 +19,16 @@ class ProjectCreateParams(TypedDict, total=False): class Config(TypedDict, total=False): + clustering_use_llm_matching: bool + + llm_matching_model: str + + llm_matching_quality_preset: str + + lower_llm_match_distance_threshold: float + max_distance: float + + query_use_llm_matching: bool + + upper_llm_match_distance_threshold: float diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 84aafa98..2b4fec42 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -9,8 +9,20 @@ class ProjectConfig(BaseModel): + clustering_use_llm_matching: Optional[bool] = None + + llm_matching_model: Optional[str] = None + + llm_matching_quality_preset: Optional[str] = None + + lower_llm_match_distance_threshold: Optional[float] = None + max_distance: Optional[float] = None + query_use_llm_matching: Optional[bool] = None + + upper_llm_match_distance_threshold: Optional[float] = None + class Project(BaseModel): id: str diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 85312729..51a6c1ad 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -9,8 +9,20 @@ class Config(BaseModel): + clustering_use_llm_matching: Optional[bool] = None + + llm_matching_model: Optional[str] = None + + llm_matching_quality_preset: Optional[str] = None + + lower_llm_match_distance_threshold: Optional[float] = None + max_distance: Optional[float] = None + query_use_llm_matching: Optional[bool] = None + + upper_llm_match_distance_threshold: Optional[float] = None + class ProjectReturnSchema(BaseModel): id: str diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 46d747ee..0a5aa540 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -17,4 +17,16 @@ class ProjectUpdateParams(TypedDict, total=False): class Config(TypedDict, total=False): + clustering_use_llm_matching: bool + + llm_matching_model: str + + llm_matching_quality_preset: str + + lower_llm_match_distance_threshold: float + max_distance: float + + query_use_llm_matching: bool + + upper_llm_match_distance_threshold: float diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index 50b5f268..d58b7bfa 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -13,6 +13,8 @@ class EntryQueryParams(TypedDict, total=False): question: Required[str] + use_llm_matching: bool + client_metadata: Optional[object] x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 5fa5ed9a..ca7eecbb 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -262,6 +262,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None: entry = client.projects.entries.query( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", + use_llm_matching=True, client_metadata={}, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -556,6 +557,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N entry = await async_client.projects.entries.query( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", question="question", + use_llm_matching=True, client_metadata={}, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 10d23461..210f4e17 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -34,7 +34,15 @@ def test_method_create(self, client: Codex) -> None: @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: project = client.projects.create( - config={"max_distance": 0}, + config={ + "clustering_use_llm_matching": True, + "llm_matching_model": "llm_matching_model", + "llm_matching_quality_preset": "llm_matching_quality_preset", + "lower_llm_match_distance_threshold": 0, + "max_distance": 0, + "query_use_llm_matching": True, + "upper_llm_match_distance_threshold": 0, + }, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", description="description", @@ -128,7 +136,15 @@ def test_method_update(self, client: Codex) -> None: def test_method_update_with_all_params(self, client: Codex) -> None: project = client.projects.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={"max_distance": 0}, + config={ + "clustering_use_llm_matching": True, + "llm_matching_model": "llm_matching_model", + "llm_matching_quality_preset": "llm_matching_quality_preset", + "lower_llm_match_distance_threshold": 0, + "max_distance": 0, + "query_use_llm_matching": True, + "upper_llm_match_distance_threshold": 0, + }, name="name", description="description", ) @@ -324,7 +340,15 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( - config={"max_distance": 0}, + config={ + "clustering_use_llm_matching": True, + "llm_matching_model": "llm_matching_model", + "llm_matching_quality_preset": "llm_matching_quality_preset", + "lower_llm_match_distance_threshold": 0, + "max_distance": 0, + "query_use_llm_matching": True, + "upper_llm_match_distance_threshold": 0, + }, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", description="description", @@ -418,7 +442,15 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={"max_distance": 0}, + config={ + "clustering_use_llm_matching": True, + "llm_matching_model": "llm_matching_model", + "llm_matching_quality_preset": "llm_matching_quality_preset", + "lower_llm_match_distance_threshold": 0, + "max_distance": 0, + "query_use_llm_matching": True, + "upper_llm_match_distance_threshold": 0, + }, name="name", description="description", ) From ae147ae779f3c8034a8bc030d96f5bfeaac779a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 04:40:49 +0000 Subject: [PATCH 090/320] fix(client): send all configured auth headers (#106) --- src/codex/_client.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index 9bdd5f02..2641513f 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -154,11 +154,7 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - if self._authenticated_api_key: - return self._authenticated_api_key - if self._public_access_key: - return self._public_access_key - return {} + return {**self._authenticated_api_key, **self._public_access_key} @property def _authenticated_api_key(self) -> dict[str, str]: @@ -386,11 +382,7 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - if self._authenticated_api_key: - return self._authenticated_api_key - if self._public_access_key: - return self._public_access_key - return {} + return {**self._authenticated_api_key, **self._public_access_key} @property def _authenticated_api_key(self) -> dict[str, str]: From a91d91e349e3a7a538c9925e20df104f2fba4bb9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 19:28:30 +0000 Subject: [PATCH 091/320] feat(api): api update (#107) --- .stats.yml | 2 +- api.md | 9 +++- src/codex/resources/projects/clusters.py | 6 ++- src/codex/resources/projects/projects.py | 9 ++-- src/codex/types/__init__.py | 1 + src/codex/types/project_retrieve_response.py | 44 +++++++++++++++++++ .../types/projects/cluster_list_params.py | 2 +- tests/api_resources/test_projects.py | 13 +++--- 8 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 src/codex/types/project_retrieve_response.py diff --git a/.stats.yml b/.stats.yml index 69bb6fce..4500dc4b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: 8ef89533cd58e3b2ceb53a877832f48b +openapi_spec_hash: ee7ad81c8308305b6a609a18615ae394 config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/api.md b/api.md index b241c15c..e9dd565d 100644 --- a/api.md +++ b/api.md @@ -135,13 +135,18 @@ Methods: Types: ```python -from codex.types import ProjectReturnSchema, ProjectListResponse, ProjectExportResponse +from codex.types import ( + ProjectReturnSchema, + ProjectRetrieveResponse, + ProjectListResponse, + ProjectExportResponse, +) ``` Methods: - client.projects.create(\*\*params) -> ProjectReturnSchema -- client.projects.retrieve(project_id) -> ProjectReturnSchema +- client.projects.retrieve(project_id) -> ProjectRetrieveResponse - client.projects.update(project_id, \*\*params) -> ProjectReturnSchema - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index 2faed31e..f376c0b1 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -53,7 +53,8 @@ def list( limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]] + | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # 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. @@ -164,7 +165,8 @@ def list( limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]] + | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # 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. diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 8a7ff1b3..ccc77268 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -48,6 +48,7 @@ from ..._base_client import make_request_options from ...types.project_list_response import ProjectListResponse from ...types.project_return_schema import ProjectReturnSchema +from ...types.project_retrieve_response import ProjectRetrieveResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -137,7 +138,7 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ProjectReturnSchema: + ) -> ProjectRetrieveResponse: """ Get a single project. @@ -157,7 +158,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectReturnSchema, + cast_to=ProjectRetrieveResponse, ) def update( @@ -409,7 +410,7 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> ProjectReturnSchema: + ) -> ProjectRetrieveResponse: """ Get a single project. @@ -429,7 +430,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectReturnSchema, + cast_to=ProjectRetrieveResponse, ) async def update( diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 8f241bc9..6c184372 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -13,5 +13,6 @@ from .project_list_response import ProjectListResponse as ProjectListResponse from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema from .project_update_params import ProjectUpdateParams as ProjectUpdateParams +from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py new file mode 100644 index 00000000..62209d32 --- /dev/null +++ b/src/codex/types/project_retrieve_response.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["ProjectRetrieveResponse", "Config"] + + +class Config(BaseModel): + clustering_use_llm_matching: Optional[bool] = None + + llm_matching_model: Optional[str] = None + + llm_matching_quality_preset: Optional[str] = None + + lower_llm_match_distance_threshold: Optional[float] = None + + max_distance: Optional[float] = None + + query_use_llm_matching: Optional[bool] = None + + upper_llm_match_distance_threshold: Optional[float] = None + + +class ProjectRetrieveResponse(BaseModel): + id: str + + config: Config + + created_at: datetime + + created_by_user_id: str + + name: str + + organization_id: str + + updated_at: datetime + + custom_rank_enabled: Optional[bool] = None + + description: Optional[str] = None diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py index 48893245..b272d806 100644 --- a/src/codex/types/projects/cluster_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -15,6 +15,6 @@ class ClusterListParams(TypedDict, total=False): order: Literal["asc", "desc"] - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]] states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 210f4e17..1e2fccbc 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -11,6 +11,7 @@ from codex.types import ( ProjectListResponse, ProjectReturnSchema, + ProjectRetrieveResponse, ) from tests.utils import assert_matches_type @@ -85,7 +86,7 @@ def test_method_retrieve(self, client: Codex) -> None: project = client.projects.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(ProjectReturnSchema, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @pytest.mark.skip() @parametrize @@ -97,7 +98,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectReturnSchema, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @pytest.mark.skip() @parametrize @@ -109,7 +110,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectReturnSchema, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -391,7 +392,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: project = await async_client.projects.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(ProjectReturnSchema, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @pytest.mark.skip() @parametrize @@ -403,7 +404,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectReturnSchema, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @pytest.mark.skip() @parametrize @@ -415,7 +416,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectReturnSchema, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True From 7819dc64ffdb333da33ac6f80211d33476b203d4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 03:40:16 +0000 Subject: [PATCH 092/320] chore(internal): slight transform perf improvement (#108) --- src/codex/_utils/_transform.py | 22 ++++++++++++++++++++++ tests/test_transform.py | 12 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index 7ac2e17f..3ec62081 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -142,6 +142,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, *, @@ -184,6 +188,15 @@ def _transform_recursive( 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): @@ -332,6 +345,15 @@ async def _async_transform_recursive( 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): diff --git a/tests/test_transform.py b/tests/test_transform.py index 324f31a7..b1ea479f 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -432,3 +432,15 @@ 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] From 4fd8f44f8e2d94af3f3a9688724ee2c22e272c7b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 02:29:52 +0000 Subject: [PATCH 093/320] feat(api): api update --- .stats.yml | 2 +- .../types/projects/cluster_list_response.py | 110 ++++++++++++++++- src/codex/types/projects/entry.py | 110 ++++++++++++++++- .../types/projects/entry_query_response.py | 111 +++++++++++++++++- 4 files changed, 329 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4500dc4b..ab9fbe2f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: ee7ad81c8308305b6a609a18615ae394 +openapi_spec_hash: 2e10455457751c9efb36adc4399c684d config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py index d13a5f98..33bc18a7 100644 --- a/src/codex/types/projects/cluster_list_response.py +++ b/src/codex/types/projects/cluster_list_response.py @@ -6,7 +6,112 @@ from ..._models import BaseModel -__all__ = ["ClusterListResponse"] +__all__ = [ + "ClusterListResponse", + "ManagedMetadata", + "ManagedMetadataContextSufficiency", + "ManagedMetadataQueryEaseCustomized", + "ManagedMetadataResponseHelpfulness", + "ManagedMetadataTrustworthiness", +] + + +class ManagedMetadataContextSufficiency(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadataQueryEaseCustomized(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadataResponseHelpfulness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadataTrustworthiness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadata(BaseModel): + latest_context: Optional[str] = None + """The most recent context string.""" + + latest_entry_point: Optional[str] = None + """The most recent entry point string.""" + + latest_llm_response: Optional[str] = None + """The most recent LLM response string.""" + + latest_location: Optional[str] = None + """The most recent location string.""" + + context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None + """Holds a list of scores and computes aggregate statistics.""" + + contexts: Optional[List[str]] = None + + entry_points: Optional[List[str]] = None + + llm_responses: Optional[List[str]] = None + + locations: Optional[List[str]] = None + + query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None + """Holds a list of scores and computes aggregate statistics.""" + + response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None + """Holds a list of scores and computes aggregate statistics.""" + + trustworthiness: Optional[ManagedMetadataTrustworthiness] = None + """Holds a list of scores and computes aggregate statistics.""" class ClusterListResponse(BaseModel): @@ -16,6 +121,9 @@ class ClusterListResponse(BaseModel): created_at: datetime + managed_metadata: ManagedMetadata + """Extract system-defined, managed metadata from client_query_metadata.""" + project_id: str question: str diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index b8eeb0ec..24d57c89 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -6,7 +6,112 @@ from ..._models import BaseModel -__all__ = ["Entry"] +__all__ = [ + "Entry", + "ManagedMetadata", + "ManagedMetadataContextSufficiency", + "ManagedMetadataQueryEaseCustomized", + "ManagedMetadataResponseHelpfulness", + "ManagedMetadataTrustworthiness", +] + + +class ManagedMetadataContextSufficiency(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadataQueryEaseCustomized(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadataResponseHelpfulness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadataTrustworthiness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class ManagedMetadata(BaseModel): + latest_context: Optional[str] = None + """The most recent context string.""" + + latest_entry_point: Optional[str] = None + """The most recent entry point string.""" + + latest_llm_response: Optional[str] = None + """The most recent LLM response string.""" + + latest_location: Optional[str] = None + """The most recent location string.""" + + context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None + """Holds a list of scores and computes aggregate statistics.""" + + contexts: Optional[List[str]] = None + + entry_points: Optional[List[str]] = None + + llm_responses: Optional[List[str]] = None + + locations: Optional[List[str]] = None + + query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None + """Holds a list of scores and computes aggregate statistics.""" + + response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None + """Holds a list of scores and computes aggregate statistics.""" + + trustworthiness: Optional[ManagedMetadataTrustworthiness] = None + """Holds a list of scores and computes aggregate statistics.""" class Entry(BaseModel): @@ -14,6 +119,9 @@ class Entry(BaseModel): created_at: datetime + managed_metadata: ManagedMetadata + """Extract system-defined, managed metadata from client_query_metadata.""" + project_id: str question: str diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py index 766e1e24..26db1d83 100644 --- a/src/codex/types/projects/entry_query_response.py +++ b/src/codex/types/projects/entry_query_response.py @@ -4,12 +4,121 @@ from ..._models import BaseModel -__all__ = ["EntryQueryResponse", "Entry"] +__all__ = [ + "EntryQueryResponse", + "Entry", + "EntryManagedMetadata", + "EntryManagedMetadataContextSufficiency", + "EntryManagedMetadataQueryEaseCustomized", + "EntryManagedMetadataResponseHelpfulness", + "EntryManagedMetadataTrustworthiness", +] + + +class EntryManagedMetadataContextSufficiency(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class EntryManagedMetadataQueryEaseCustomized(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class EntryManagedMetadataResponseHelpfulness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class EntryManagedMetadataTrustworthiness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + +class EntryManagedMetadata(BaseModel): + latest_context: Optional[str] = None + """The most recent context string.""" + + latest_entry_point: Optional[str] = None + """The most recent entry point string.""" + + latest_llm_response: Optional[str] = None + """The most recent LLM response string.""" + + latest_location: Optional[str] = None + """The most recent location string.""" + + context_sufficiency: Optional[EntryManagedMetadataContextSufficiency] = None + """Holds a list of scores and computes aggregate statistics.""" + + contexts: Optional[List[str]] = None + + entry_points: Optional[List[str]] = None + + llm_responses: Optional[List[str]] = None + + locations: Optional[List[str]] = None + + query_ease_customized: Optional[EntryManagedMetadataQueryEaseCustomized] = None + """Holds a list of scores and computes aggregate statistics.""" + + response_helpfulness: Optional[EntryManagedMetadataResponseHelpfulness] = None + """Holds a list of scores and computes aggregate statistics.""" + + trustworthiness: Optional[EntryManagedMetadataTrustworthiness] = None + """Holds a list of scores and computes aggregate statistics.""" class Entry(BaseModel): id: str + managed_metadata: EntryManagedMetadata + """Extract system-defined, managed metadata from client_query_metadata.""" + question: str answer: Optional[str] = None From 48b70386c62b026a912dd3d9358ed0eeb02a94ed Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 04:04:24 +0000 Subject: [PATCH 094/320] chore(internal): expand CI branch coverage --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ac5f63f..b8920208 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,18 @@ name: CI on: push: - branches: - - main - pull_request: - branches: - - main - - next + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'preview-head/**' + - 'preview-base/**' + - 'preview/**' jobs: lint: name: lint runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 From 8bdffb4656784378d6a8d9ba7c5503e08788332c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 04:08:38 +0000 Subject: [PATCH 095/320] chore(internal): reduce CI branch coverage --- .github/workflows/ci.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8920208..e8b72361 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,12 @@ name: CI on: push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'preview-head/**' - - 'preview-base/**' - - 'preview/**' + branches: + - main + pull_request: + branches: + - main + - next jobs: lint: From 3b32bc4fc8eca38d0e462a91050aa209b6fad3c9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 02:09:29 +0000 Subject: [PATCH 096/320] fix(perf): skip traversing types for NotGiven values --- src/codex/_utils/_transform.py | 11 +++++++++++ tests/test_transform.py | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index 3ec62081..3b2b8e00 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -12,6 +12,7 @@ from ._utils import ( is_list, + is_given, is_mapping, is_iterable, ) @@ -258,6 +259,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 `NotGiven` 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 @@ -415,6 +421,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 `NotGiven` 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 diff --git a/tests/test_transform.py b/tests/test_transform.py index b1ea479f..527845a0 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from codex._types import Base64FileInput +from codex._types import NOT_GIVEN, Base64FileInput from codex._utils import ( PropertyInfo, transform as _transform, @@ -444,3 +444,10 @@ async def test_transform_skipping(use_async: bool) -> None: # 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) == {} From 026df181e6103e6c1cd8581973c4a9958f86b5e3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 02:11:14 +0000 Subject: [PATCH 097/320] fix(perf): optimize some hot paths --- src/codex/_utils/_transform.py | 14 +++++++++++++- src/codex/_utils/_typing.py | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index 3b2b8e00..b0cc20a7 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -5,7 +5,7 @@ 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 @@ -13,6 +13,7 @@ from ._utils import ( is_list, is_given, + lru_cache, is_mapping, is_iterable, ) @@ -109,6 +110,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. @@ -433,3 +435,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/codex/_utils/_typing.py b/src/codex/_utils/_typing.py index 278749b1..1958820f 100644 --- a/src/codex/_utils/_typing.py +++ b/src/codex/_utils/_typing.py @@ -13,6 +13,7 @@ get_origin, ) +from ._utils import lru_cache from .._types import InheritsGeneric from .._compat import is_union as _is_union @@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: # 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])) From 9d57264e11ccb23719e513e3f598079dcb273364 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 02:11:58 +0000 Subject: [PATCH 098/320] chore(internal): update pyright settings --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ff15445f..e4307cda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,6 +147,7 @@ exclude = [ ] reportImplicitOverride = true +reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false From 6e05ce67d32ed65184238e3acaa2e792a6ea1feb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 02:12:59 +0000 Subject: [PATCH 099/320] chore(client): minor internal fixes --- src/codex/_base_client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 273341b1..564ab8a7 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -409,7 +409,8 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 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() + options.idempotency_key = options.idempotency_key or self._idempotency_key() + 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. @@ -943,6 +944,10 @@ def _request( request = self._build_request(options, retries_taken=retries_taken) self._prepare_request(request) + if options.idempotency_key: + # ensure the idempotency key is reused between requests + input_options.idempotency_key = options.idempotency_key + kwargs: HttpxSendArgs = {} if self.custom_auth is not None: kwargs["auth"] = self.custom_auth @@ -1475,6 +1480,10 @@ async def _request( request = self._build_request(options, retries_taken=retries_taken) await self._prepare_request(request) + if options.idempotency_key: + # ensure the idempotency key is reused between requests + input_options.idempotency_key = options.idempotency_key + kwargs: HttpxSendArgs = {} if self.custom_auth is not None: kwargs["auth"] = self.custom_auth From a8b0e062c53925ff56a197915fd690e5129d8c3d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 23:36:45 +0000 Subject: [PATCH 100/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b0699969..08e82c45 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.14" + ".": "0.1.0-alpha.15" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e4307cda..6d0ec848 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.14" +version = "0.1.0-alpha.15" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 15097538..d4919cf4 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.14" # x-release-please-version +__version__ = "0.1.0-alpha.15" # x-release-please-version From 17b36437163075abc0dcdcc4e5269ac1cc11c089 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 20:16:07 +0000 Subject: [PATCH 101/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/clusters.py | 8 ++++++-- src/codex/types/projects/cluster_list_params.py | 4 +++- src/codex/types/projects/cluster_list_response.py | 4 ++++ src/codex/types/projects/entry.py | 4 ++++ tests/api_resources/projects/test_clusters.py | 2 ++ 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index ab9fbe2f..c1c522e8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: 2e10455457751c9efb36adc4399c684d +openapi_spec_hash: 6311021a3aba7ac56cc3b474762945c0 config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index f376c0b1..e35e8ae8 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -50,10 +50,11 @@ def list( self, project_id: str, *, + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]] + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]] | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -87,6 +88,7 @@ def list( timeout=timeout, query=maybe_transform( { + "eval_issue_types": eval_issue_types, "limit": limit, "offset": offset, "order": order, @@ -162,10 +164,11 @@ def list( self, project_id: str, *, + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]] + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]] | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -199,6 +202,7 @@ def list( timeout=timeout, query=maybe_transform( { + "eval_issue_types": eval_issue_types, "limit": limit, "offset": offset, "order": order, diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py index b272d806..d1d818e3 100644 --- a/src/codex/types/projects/cluster_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -9,12 +9,14 @@ class ClusterListParams(TypedDict, total=False): + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] + limit: int offset: int order: Literal["asc", "desc"] - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]] + sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]] states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py index 33bc18a7..c149fbdb 100644 --- a/src/codex/types/projects/cluster_list_response.py +++ b/src/codex/types/projects/cluster_list_response.py @@ -140,6 +140,10 @@ class ClusterListResponse(BaseModel): draft_answer_last_edited: Optional[datetime] = None + eval_issue_type: Optional[str] = None + + eval_score: Optional[float] = None + frequency_count: Optional[int] = None """number of times the entry matched for a /query request""" diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index 24d57c89..638fe352 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -138,5 +138,9 @@ class Entry(BaseModel): draft_answer_last_edited: Optional[datetime] = None + eval_issue_type: Optional[str] = None + + eval_score: Optional[float] = None + frequency_count: Optional[int] = None """number of times the entry matched for a /query request""" diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py index 5a464970..53706c6c 100644 --- a/tests/api_resources/projects/test_clusters.py +++ b/tests/api_resources/projects/test_clusters.py @@ -31,6 +31,7 @@ def test_method_list(self, client: Codex) -> None: def test_method_list_with_all_params(self, client: Codex) -> None: cluster = client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + eval_issue_types=["hallucination"], limit=1, offset=0, order="asc", @@ -144,6 +145,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: cluster = await async_client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + eval_issue_types=["hallucination"], limit=1, offset=0, order="asc", From a488e268e99c6d724536625ec4b383ccff44dbca Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 02:12:31 +0000 Subject: [PATCH 102/320] chore(internal): bump pyright version --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- src/codex/_base_client.py | 6 +++++- src/codex/_models.py | 1 - src/codex/_utils/_typing.py | 2 +- tests/conftest.py | 2 +- tests/test_models.py | 2 +- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6d0ec848..cf867da1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ Repository = "https://github.com/cleanlab/codex-python" managed = true # version pins are in requirements-dev.lock dev-dependencies = [ - "pyright>=1.1.359", + "pyright==1.1.399", "mypy", "respx", "pytest", diff --git a/requirements-dev.lock b/requirements-dev.lock index 5b7189d6..7fcfe657 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -69,7 +69,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.392.post0 +pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 564ab8a7..41366043 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -98,7 +98,11 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT diff --git a/src/codex/_models.py b/src/codex/_models.py index 34935716..58b9263e 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -19,7 +19,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py index 1958820f..1bac9542 100644 --- a/src/codex/_utils/_typing.py +++ b/src/codex/_utils/_typing.py @@ -110,7 +110,7 @@ class MyResponse(Foo[_T]): ``` """ cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] # we're given the class directly return extract_type_arg(typ, index) diff --git a/tests/conftest.py b/tests/conftest.py index 8823ef7a..c93053b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ from codex import Codex, AsyncCodex if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") diff --git a/tests/test_models.py b/tests/test_models.py index 73e6d67c..fb9d99b1 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -832,7 +832,7 @@ class B(BaseModel): @pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: - Alias = TypeAliasType("Alias", str) + Alias = TypeAliasType("Alias", str) # pyright: ignore class Model(BaseModel): alias: Alias From 1c475ba19f678a7e4c6feee9c6e3b6122bfed1ae Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 02:13:02 +0000 Subject: [PATCH 103/320] chore(internal): base client updates --- src/codex/_base_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 41366043..69be76db 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -119,6 +119,7 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( @@ -134,19 +135,30 @@ def __init__( params: Query, ) -> None: ... + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + def __init__( self, *, url: URL | NotGiven = NOT_GIVEN, + json: Body | NotGiven = NOT_GIVEN, params: Query | NotGiven = NOT_GIVEN, ) -> None: self.url = url + self.json = json self.params = params @override def __repr__(self) -> str: if self.url: return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" return f"{self.__class__.__name__}(params={self.params})" @@ -195,6 +207,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") From 3d06a2b7e271dfd663b082aa6b8d04f5b9e7bf52 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 22:21:35 +0000 Subject: [PATCH 104/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 08e82c45..7e56fe29 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.15" + ".": "0.1.0-alpha.16" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index cf867da1..3b48a5bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.15" +version = "0.1.0-alpha.16" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index d4919cf4..b99310c3 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.15" # x-release-please-version +__version__ = "0.1.0-alpha.16" # x-release-please-version From 0bce1e1cd33071e999df7580af81073e2d4235fe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 02:14:16 +0000 Subject: [PATCH 105/320] chore(internal): update models test --- tests/test_models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index fb9d99b1..c96609ce 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -492,12 +492,15 @@ class Model(BaseModel): resource_id: Optional[str] = None m = Model.construct() + assert m.resource_id is None assert "resource_id" not in m.model_fields_set m = Model.construct(resource_id=None) + assert m.resource_id is None assert "resource_id" in m.model_fields_set m = Model.construct(resource_id="foo") + assert m.resource_id == "foo" assert "resource_id" in m.model_fields_set From eb7b90ed7dfd25c5ab3a7a42c3d3917cd3118c6c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 20 Apr 2025 23:16:04 +0000 Subject: [PATCH 106/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/clusters.py | 6 ++++-- src/codex/types/projects/cluster_list_params.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index c1c522e8..f033bbda 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: 6311021a3aba7ac56cc3b474762945c0 +openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index e35e8ae8..584cde18 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -50,7 +50,8 @@ def list( self, project_id: str, *, - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN, + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, @@ -164,7 +165,8 @@ def list( self, project_id: str, *, - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN, + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py index d1d818e3..fa84a6b0 100644 --- a/src/codex/types/projects/cluster_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -9,7 +9,7 @@ class ClusterListParams(TypedDict, total=False): - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] limit: int From 1f1f5accb70873ff984b938c7d17be119b23e6c1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:41:55 +0000 Subject: [PATCH 107/320] feat(api): add project increment_queries and other recent endpoints --- .stats.yml | 4 +- api.md | 15 +- .../resources/organizations/organizations.py | 158 ++++++++ src/codex/resources/projects/entries.py | 289 +++++++++++++- src/codex/resources/projects/projects.py | 78 ++++ src/codex/types/__init__.py | 4 + .../organization_list_members_response.py | 17 + ...anization_retrieve_permissions_response.py | 8 + src/codex/types/projects/__init__.py | 2 + .../types/projects/entry_notify_sme_params.py | 21 + .../projects/entry_notify_sme_response.py | 14 + tests/api_resources/projects/test_entries.py | 366 +++++++++++++++++- tests/api_resources/test_organizations.py | 174 ++++++++- tests/api_resources/test_projects.py | 84 ++++ 14 files changed, 1227 insertions(+), 7 deletions(-) create mode 100644 src/codex/types/organization_list_members_response.py create mode 100644 src/codex/types/organization_retrieve_permissions_response.py create mode 100644 src/codex/types/projects/entry_notify_sme_params.py create mode 100644 src/codex/types/projects/entry_notify_sme_response.py diff --git a/.stats.yml b/.stats.yml index f033bbda..d78252d8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 36 +configured_endpoints: 42 openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d -config_hash: adbedb6317fca6f566f54564cc341846 +config_hash: 93ac12138700569dc57329400410c0fd diff --git a/api.md b/api.md index e9dd565d..5a314a2d 100644 --- a/api.md +++ b/api.md @@ -16,12 +16,18 @@ Methods: Types: ```python -from codex.types import OrganizationSchemaPublic +from codex.types import ( + OrganizationSchemaPublic, + OrganizationListMembersResponse, + OrganizationRetrievePermissionsResponse, +) ``` Methods: - client.organizations.retrieve(organization_id) -> OrganizationSchemaPublic +- client.organizations.list_members(organization_id) -> OrganizationListMembersResponse +- client.organizations.retrieve_permissions(organization_id) -> OrganizationRetrievePermissionsResponse ## Billing @@ -140,6 +146,7 @@ from codex.types import ( ProjectRetrieveResponse, ProjectListResponse, ProjectExportResponse, + ProjectIncrementQueriesResponse, ) ``` @@ -151,6 +158,7 @@ Methods: - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None - client.projects.export(project_id) -> object +- client.projects.increment_queries(project_id) -> object ## AccessKeys @@ -179,7 +187,7 @@ Methods: Types: ```python -from codex.types.projects import Entry, EntryQueryResponse +from codex.types.projects import Entry, EntryNotifySmeResponse, EntryQueryResponse ``` Methods: @@ -188,7 +196,10 @@ Methods: - client.projects.entries.retrieve(entry_id, \*, project_id) -> Entry - client.projects.entries.update(entry_id, \*, project_id, \*\*params) -> Entry - client.projects.entries.delete(entry_id, \*, project_id) -> None +- client.projects.entries.notify_sme(entry_id, \*, project_id, \*\*params) -> EntryNotifySmeResponse +- client.projects.entries.publish_draft_answer(entry_id, \*, project_id) -> Entry - client.projects.entries.query(project_id, \*\*params) -> EntryQueryResponse +- client.projects.entries.unpublish_answer(entry_id, \*, project_id) -> Entry ## Clusters diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py index 025ecba4..f1eb4d5e 100644 --- a/src/codex/resources/organizations/organizations.py +++ b/src/codex/resources/organizations/organizations.py @@ -23,6 +23,8 @@ AsyncBillingResourceWithStreamingResponse, ) from ...types.organization_schema_public import OrganizationSchemaPublic +from ...types.organization_list_members_response import OrganizationListMembersResponse +from ...types.organization_retrieve_permissions_response import OrganizationRetrievePermissionsResponse __all__ = ["OrganizationsResource", "AsyncOrganizationsResource"] @@ -84,6 +86,72 @@ def retrieve( cast_to=OrganizationSchemaPublic, ) + def list_members( + self, + organization_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, + ) -> OrganizationListMembersResponse: + """ + Get a list of organization members with their names and emails. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/members", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationListMembersResponse, + ) + + def retrieve_permissions( + self, + organization_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, + ) -> OrganizationRetrievePermissionsResponse: + """ + Get the user's permissions for this organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/permissions", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationRetrievePermissionsResponse, + ) + class AsyncOrganizationsResource(AsyncAPIResource): @cached_property @@ -142,6 +210,72 @@ async def retrieve( cast_to=OrganizationSchemaPublic, ) + async def list_members( + self, + organization_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, + ) -> OrganizationListMembersResponse: + """ + Get a list of organization members with their names and emails. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/members", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationListMembersResponse, + ) + + async def retrieve_permissions( + self, + organization_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, + ) -> OrganizationRetrievePermissionsResponse: + """ + Get the user's permissions for this organization. + + 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 organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/permissions", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationRetrievePermissionsResponse, + ) + class OrganizationsResourceWithRawResponse: def __init__(self, organizations: OrganizationsResource) -> None: @@ -150,6 +284,12 @@ def __init__(self, organizations: OrganizationsResource) -> None: self.retrieve = to_raw_response_wrapper( organizations.retrieve, ) + self.list_members = to_raw_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = to_raw_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> BillingResourceWithRawResponse: @@ -163,6 +303,12 @@ def __init__(self, organizations: AsyncOrganizationsResource) -> None: self.retrieve = async_to_raw_response_wrapper( organizations.retrieve, ) + self.list_members = async_to_raw_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = async_to_raw_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> AsyncBillingResourceWithRawResponse: @@ -176,6 +322,12 @@ def __init__(self, organizations: OrganizationsResource) -> None: self.retrieve = to_streamed_response_wrapper( organizations.retrieve, ) + self.list_members = to_streamed_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = to_streamed_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> BillingResourceWithStreamingResponse: @@ -189,6 +341,12 @@ def __init__(self, organizations: AsyncOrganizationsResource) -> None: self.retrieve = async_to_streamed_response_wrapper( organizations.retrieve, ) + self.list_members = async_to_streamed_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = async_to_streamed_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> AsyncBillingResourceWithStreamingResponse: diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index f0fa39cb..f88e2243 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -21,9 +21,10 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.projects import entry_query_params, entry_create_params, entry_update_params +from ...types.projects import entry_query_params, entry_create_params, entry_update_params, entry_notify_sme_params from ...types.projects.entry import Entry from ...types.projects.entry_query_response import EntryQueryResponse +from ...types.projects.entry_notify_sme_response import EntryNotifySmeResponse __all__ = ["EntriesResource", "AsyncEntriesResource"] @@ -229,6 +230,92 @@ def delete( cast_to=NoneType, ) + def notify_sme( + self, + entry_id: str, + *, + project_id: str, + email: str, + view_context: entry_notify_sme_params.ViewContext, + # 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, + ) -> EntryNotifySmeResponse: + """ + Notify a subject matter expert to review and answer a specific entry. + + Returns: SMENotificationResponse with status and notification details + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._post( + f"/api/projects/{project_id}/entries/{entry_id}/notifications", + body=maybe_transform( + { + "email": email, + "view_context": view_context, + }, + entry_notify_sme_params.EntryNotifySmeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EntryNotifySmeResponse, + ) + + def publish_draft_answer( + self, + entry_id: str, + *, + project_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, + ) -> Entry: + """Promote a draft answer to a published answer for a knowledge entry. + + This always + results in the entry's draft answer being removed. If the entry already has a + published answer, it will be overwritten and permanently lost. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._put( + f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + def query( self, project_id: str, @@ -291,6 +378,45 @@ def query( cast_to=EntryQueryResponse, ) + def unpublish_answer( + self, + entry_id: str, + *, + project_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, + ) -> Entry: + """Unpublish an answer for a knowledge entry. + + This always results in the entry's + answer being removed. If the entry does not already have a draft answer, the + current answer will be retained as the draft answer. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._put( + f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + class AsyncEntriesResource(AsyncAPIResource): @cached_property @@ -493,6 +619,92 @@ async def delete( cast_to=NoneType, ) + async def notify_sme( + self, + entry_id: str, + *, + project_id: str, + email: str, + view_context: entry_notify_sme_params.ViewContext, + # 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, + ) -> EntryNotifySmeResponse: + """ + Notify a subject matter expert to review and answer a specific entry. + + Returns: SMENotificationResponse with status and notification details + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._post( + f"/api/projects/{project_id}/entries/{entry_id}/notifications", + body=await async_maybe_transform( + { + "email": email, + "view_context": view_context, + }, + entry_notify_sme_params.EntryNotifySmeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EntryNotifySmeResponse, + ) + + async def publish_draft_answer( + self, + entry_id: str, + *, + project_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, + ) -> Entry: + """Promote a draft answer to a published answer for a knowledge entry. + + This always + results in the entry's draft answer being removed. If the entry already has a + published answer, it will be overwritten and permanently lost. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._put( + f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + async def query( self, project_id: str, @@ -557,6 +769,45 @@ async def query( cast_to=EntryQueryResponse, ) + async def unpublish_answer( + self, + entry_id: str, + *, + project_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, + ) -> Entry: + """Unpublish an answer for a knowledge entry. + + This always results in the entry's + answer being removed. If the entry does not already have a draft answer, the + current answer will be retained as the draft answer. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._put( + f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + class EntriesResourceWithRawResponse: def __init__(self, entries: EntriesResource) -> None: @@ -574,9 +825,18 @@ def __init__(self, entries: EntriesResource) -> None: self.delete = to_raw_response_wrapper( entries.delete, ) + self.notify_sme = to_raw_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = to_raw_response_wrapper( + entries.publish_draft_answer, + ) self.query = to_raw_response_wrapper( entries.query, ) + self.unpublish_answer = to_raw_response_wrapper( + entries.unpublish_answer, + ) class AsyncEntriesResourceWithRawResponse: @@ -595,9 +855,18 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.delete = async_to_raw_response_wrapper( entries.delete, ) + self.notify_sme = async_to_raw_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = async_to_raw_response_wrapper( + entries.publish_draft_answer, + ) self.query = async_to_raw_response_wrapper( entries.query, ) + self.unpublish_answer = async_to_raw_response_wrapper( + entries.unpublish_answer, + ) class EntriesResourceWithStreamingResponse: @@ -616,9 +885,18 @@ def __init__(self, entries: EntriesResource) -> None: self.delete = to_streamed_response_wrapper( entries.delete, ) + self.notify_sme = to_streamed_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = to_streamed_response_wrapper( + entries.publish_draft_answer, + ) self.query = to_streamed_response_wrapper( entries.query, ) + self.unpublish_answer = to_streamed_response_wrapper( + entries.unpublish_answer, + ) class AsyncEntriesResourceWithStreamingResponse: @@ -637,6 +915,15 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.delete = async_to_streamed_response_wrapper( entries.delete, ) + self.notify_sme = async_to_streamed_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = async_to_streamed_response_wrapper( + entries.publish_draft_answer, + ) self.query = async_to_streamed_response_wrapper( entries.query, ) + self.unpublish_answer = async_to_streamed_response_wrapper( + entries.unpublish_answer, + ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index ccc77268..47639a5a 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -324,6 +324,39 @@ def export( cast_to=object, ) + def increment_queries( + self, + project_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, + ) -> object: + """ + Increment the queries metric for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/increment_queries", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=object, + ) + class AsyncProjectsResource(AsyncAPIResource): @cached_property @@ -596,6 +629,39 @@ async def export( cast_to=object, ) + async def increment_queries( + self, + project_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, + ) -> object: + """ + Increment the queries metric for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/increment_queries", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=object, + ) + class ProjectsResourceWithRawResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -619,6 +685,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_raw_response_wrapper( projects.export, ) + self.increment_queries = to_raw_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithRawResponse: @@ -655,6 +724,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_raw_response_wrapper( projects.export, ) + self.increment_queries = async_to_raw_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: @@ -691,6 +763,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_streamed_response_wrapper( projects.export, ) + self.increment_queries = to_streamed_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithStreamingResponse: @@ -727,6 +802,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_streamed_response_wrapper( projects.export, ) + self.increment_queries = async_to_streamed_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 6c184372..3e65a680 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -16,3 +16,7 @@ from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams +from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse +from .organization_retrieve_permissions_response import ( + OrganizationRetrievePermissionsResponse as OrganizationRetrievePermissionsResponse, +) diff --git a/src/codex/types/organization_list_members_response.py b/src/codex/types/organization_list_members_response.py new file mode 100644 index 00000000..37897d56 --- /dev/null +++ b/src/codex/types/organization_list_members_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .._models import BaseModel + +__all__ = ["OrganizationListMembersResponse", "OrganizationListMembersResponseItem"] + + +class OrganizationListMembersResponseItem(BaseModel): + email: str + + name: str + + +OrganizationListMembersResponse: TypeAlias = List[OrganizationListMembersResponseItem] diff --git a/src/codex/types/organization_retrieve_permissions_response.py b/src/codex/types/organization_retrieve_permissions_response.py new file mode 100644 index 00000000..ee7b0671 --- /dev/null +++ b/src/codex/types/organization_retrieve_permissions_response.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict +from typing_extensions import TypeAlias + +__all__ = ["OrganizationRetrievePermissionsResponse"] + +OrganizationRetrievePermissionsResponse: TypeAlias = Dict[str, bool] diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 2733406c..2b69e570 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -10,9 +10,11 @@ from .entry_update_params import EntryUpdateParams as EntryUpdateParams from .entry_query_response import EntryQueryResponse as EntryQueryResponse from .cluster_list_response import ClusterListResponse as ClusterListResponse +from .entry_notify_sme_params import EntryNotifySmeParams as EntryNotifySmeParams from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams +from .entry_notify_sme_response import EntryNotifySmeResponse as EntryNotifySmeResponse from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse from .access_key_retrieve_project_id_response import ( AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse, diff --git a/src/codex/types/projects/entry_notify_sme_params.py b/src/codex/types/projects/entry_notify_sme_params.py new file mode 100644 index 00000000..409f8bc5 --- /dev/null +++ b/src/codex/types/projects/entry_notify_sme_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["EntryNotifySmeParams", "ViewContext"] + + +class EntryNotifySmeParams(TypedDict, total=False): + project_id: Required[str] + + email: Required[str] + + view_context: Required[ViewContext] + + +class ViewContext(TypedDict, total=False): + page: Required[int] + + filter: Literal["unanswered", "answered", "all", "hallucination", "search_failure", "unhelpful", "difficult_query"] diff --git a/src/codex/types/projects/entry_notify_sme_response.py b/src/codex/types/projects/entry_notify_sme_response.py new file mode 100644 index 00000000..dd05a6cf --- /dev/null +++ b/src/codex/types/projects/entry_notify_sme_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from ..._models import BaseModel + +__all__ = ["EntryNotifySmeResponse"] + + +class EntryNotifySmeResponse(BaseModel): + entry_id: str + + recipient_email: str + + status: str diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index ca7eecbb..31a5e408 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -9,7 +9,11 @@ from codex import Codex, AsyncCodex from tests.utils import assert_matches_type -from codex.types.projects import Entry, EntryQueryResponse +from codex.types.projects import ( + Entry, + EntryQueryResponse, + EntryNotifySmeResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -247,6 +251,134 @@ def test_path_params_delete(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) + @pytest.mark.skip() + @parametrize + def test_method_notify_sme(self, client: Codex) -> None: + entry = client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_notify_sme_with_all_params(self, client: Codex) -> None: + entry = client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={ + "page": 0, + "filter": "unanswered", + }, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_notify_sme(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_notify_sme(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_notify_sme(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + email="email", + view_context={"page": 0}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.notify_sme( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + @pytest.mark.skip() + @parametrize + def test_method_publish_draft_answer(self, client: Codex) -> None: + entry = client.projects.entries.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_publish_draft_answer(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_publish_draft_answer(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_publish_draft_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize def test_method_query(self, client: Codex) -> None: @@ -308,6 +440,58 @@ def test_path_params_query(self, client: Codex) -> None: question="question", ) + @pytest.mark.skip() + @parametrize + def test_method_unpublish_answer(self, client: Codex) -> None: + entry = client.projects.entries.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_unpublish_answer(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_unpublish_answer(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_unpublish_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.unpublish_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + class TestAsyncEntries: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -542,6 +726,134 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) + @pytest.mark.skip() + @parametrize + async def test_method_notify_sme(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_notify_sme_with_all_params(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={ + "page": 0, + "filter": "unanswered", + }, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_notify_sme(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_notify_sme(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_notify_sme(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + email="email", + view_context={"page": 0}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.notify_sme( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + @pytest.mark.skip() + @parametrize + async def test_method_publish_draft_answer(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_publish_draft_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_publish_draft_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_publish_draft_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize async def test_method_query(self, async_client: AsyncCodex) -> None: @@ -602,3 +914,55 @@ async def test_path_params_query(self, async_client: AsyncCodex) -> None: project_id="", question="question", ) + + @pytest.mark.skip() + @parametrize + async def test_method_unpublish_answer(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_unpublish_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_unpublish_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_unpublish_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.unpublish_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index 1a13f77a..c665e85c 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -8,7 +8,11 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import OrganizationSchemaPublic +from codex.types import ( + OrganizationSchemaPublic, + OrganizationListMembersResponse, + OrganizationRetrievePermissionsResponse, +) from tests.utils import assert_matches_type base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -59,6 +63,90 @@ def test_path_params_retrieve(self, client: Codex) -> None: "", ) + @pytest.mark.skip() + @parametrize + def test_method_list_members(self, client: Codex) -> None: + organization = client.organizations.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list_members(self, client: Codex) -> None: + response = client.organizations.with_raw_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list_members(self, client: Codex) -> None: + with client.organizations.with_streaming_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list_members(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.with_raw_response.list_members( + "", + ) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve_permissions(self, client: Codex) -> None: + organization = client.organizations.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve_permissions(self, client: Codex) -> None: + response = client.organizations.with_raw_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve_permissions(self, client: Codex) -> None: + with client.organizations.with_streaming_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve_permissions(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.with_raw_response.retrieve_permissions( + "", + ) + class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -104,3 +192,87 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: await async_client.organizations.with_raw_response.retrieve( "", ) + + @pytest.mark.skip() + @parametrize + async def test_method_list_members(self, async_client: AsyncCodex) -> None: + organization = await async_client.organizations.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list_members(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.with_raw_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = await response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list_members(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.with_streaming_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = await response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list_members(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.with_raw_response.list_members( + "", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_permissions(self, async_client: AsyncCodex) -> None: + organization = await async_client.organizations.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve_permissions(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.with_raw_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = await response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve_permissions(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.with_streaming_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = await response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve_permissions(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.with_raw_response.retrieve_permissions( + "", + ) diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 1e2fccbc..bfa657d2 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -323,6 +323,48 @@ def test_path_params_export(self, client: Codex) -> None: "", ) + @pytest.mark.skip() + @parametrize + def test_method_increment_queries(self, client: Codex) -> None: + project = client.projects.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_increment_queries(self, client: Codex) -> None: + response = client.projects.with_raw_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_increment_queries(self, client: Codex) -> None: + with client.projects.with_streaming_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(object, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_increment_queries(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.increment_queries( + "", + ) + class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -628,3 +670,45 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None: await async_client.projects.with_raw_response.export( "", ) + + @pytest.mark.skip() + @parametrize + async def test_method_increment_queries(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(object, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.increment_queries( + "", + ) From c5d26f8f913a877c30bc2fcccea86dbb5199f210 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:48:01 +0000 Subject: [PATCH 108/320] feat(api): add project increment_queries and other recent endpoints --- .stats.yml | 2 +- api.md | 12 +++--------- src/codex/resources/users/myself/api_key.py | 10 +++++----- src/codex/resources/users/myself/myself.py | 10 +++++----- src/codex/resources/users/users.py | 10 +++++----- src/codex/types/__init__.py | 1 - src/codex/types/users/__init__.py | 1 + .../{user.py => users/user_schema_public.py} | 6 +++--- tests/api_resources/test_users.py | 18 +++++++++--------- .../api_resources/users/myself/test_api_key.py | 15 +++++++-------- tests/api_resources/users/test_myself.py | 14 +++++++------- 11 files changed, 46 insertions(+), 53 deletions(-) rename src/codex/types/{user.py => users/user_schema_public.py} (80%) diff --git a/.stats.yml b/.stats.yml index d78252d8..aa1535bd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d -config_hash: 93ac12138700569dc57329400410c0fd +config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/api.md b/api.md index 5a314a2d..617cdd24 100644 --- a/api.md +++ b/api.md @@ -83,15 +83,9 @@ Methods: # Users -Types: - -```python -from codex.types import User -``` - Methods: -- client.users.activate_account(\*\*params) -> User +- client.users.activate_account(\*\*params) -> UserSchemaPublic ## Myself @@ -103,13 +97,13 @@ from codex.types.users import UserSchema, UserSchemaPublic Methods: -- client.users.myself.retrieve() -> User +- client.users.myself.retrieve() -> UserSchemaPublic ### APIKey Methods: -- client.users.myself.api_key.retrieve() -> User +- client.users.myself.api_key.retrieve() -> UserSchemaPublic - client.users.myself.api_key.refresh() -> UserSchema ### Organizations diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py index 87f647d3..72f1502b 100644 --- a/src/codex/resources/users/myself/api_key.py +++ b/src/codex/resources/users/myself/api_key.py @@ -13,9 +13,9 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ....types.user import User from ...._base_client import make_request_options from ....types.users.user_schema import UserSchema +from ....types.users.user_schema_public import UserSchemaPublic __all__ = ["APIKeyResource", "AsyncAPIKeyResource"] @@ -49,14 +49,14 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user when authenticated with API key.""" return self._get( "/api/users/myself/api-key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) def refresh( @@ -108,14 +108,14 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user when authenticated with API key.""" return await self._get( "/api/users/myself/api-key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) async def refresh( diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py index 2cc4868b..3ee27229 100644 --- a/src/codex/resources/users/myself/myself.py +++ b/src/codex/resources/users/myself/myself.py @@ -21,7 +21,6 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ....types.user import User from .organizations import ( OrganizationsResource, AsyncOrganizationsResource, @@ -31,6 +30,7 @@ AsyncOrganizationsResourceWithStreamingResponse, ) from ...._base_client import make_request_options +from ....types.users.user_schema_public import UserSchemaPublic __all__ = ["MyselfResource", "AsyncMyselfResource"] @@ -72,14 +72,14 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user info for frontend.""" return self._get( "/api/users/myself", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) @@ -120,14 +120,14 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user info for frontend.""" return await self._get( "/api/users/myself", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index a7d9d2ab..c276f950 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -21,7 +21,6 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ...types.user import User from .verification import ( VerificationResource, AsyncVerificationResource, @@ -39,6 +38,7 @@ AsyncMyselfResourceWithStreamingResponse, ) from ..._base_client import make_request_options +from ...types.users.user_schema_public import UserSchemaPublic __all__ = ["UsersResource", "AsyncUsersResource"] @@ -87,7 +87,7 @@ def activate_account( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """ Activate an authenticated user's account @@ -117,7 +117,7 @@ def activate_account( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) @@ -165,7 +165,7 @@ async def activate_account( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """ Activate an authenticated user's account @@ -195,7 +195,7 @@ async def activate_account( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 3e65a680..008c4a67 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations -from .user import User as User from .tlm_score_params import TlmScoreParams as TlmScoreParams from .tlm_prompt_params import TlmPromptParams as TlmPromptParams from .tlm_score_response import TlmScoreResponse as TlmScoreResponse diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py index 438bc6f3..23dbc910 100644 --- a/src/codex/types/users/__init__.py +++ b/src/codex/types/users/__init__.py @@ -3,4 +3,5 @@ from __future__ import annotations from .user_schema import UserSchema as UserSchema +from .user_schema_public import UserSchemaPublic as UserSchemaPublic from .verification_resend_response import VerificationResendResponse as VerificationResendResponse diff --git a/src/codex/types/user.py b/src/codex/types/users/user_schema_public.py similarity index 80% rename from src/codex/types/user.py rename to src/codex/types/users/user_schema_public.py index 3d7ec233..181113b0 100644 --- a/src/codex/types/user.py +++ b/src/codex/types/users/user_schema_public.py @@ -2,12 +2,12 @@ from typing import Optional -from .._models import BaseModel +from ..._models import BaseModel -__all__ = ["User"] +__all__ = ["UserSchemaPublic"] -class User(BaseModel): +class UserSchemaPublic(BaseModel): id: str api_key: str diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index 7e78b99e..101d0f66 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -8,9 +8,9 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import User from tests.utils import assert_matches_type from codex._utils import parse_datetime +from codex.types.users import UserSchemaPublic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -25,7 +25,7 @@ def test_method_activate_account(self, client: Codex) -> None: first_name="first_name", last_name="last_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -39,7 +39,7 @@ def test_method_activate_account_with_all_params(self, client: Codex) -> None: phone_number="phone_number", user_provided_company_name="user_provided_company_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -52,7 +52,7 @@ def test_raw_response_activate_account(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -65,7 +65,7 @@ def test_streaming_response_activate_account(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -80,7 +80,7 @@ async def test_method_activate_account(self, async_client: AsyncCodex) -> None: first_name="first_name", last_name="last_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -94,7 +94,7 @@ async def test_method_activate_account_with_all_params(self, async_client: Async phone_number="phone_number", user_provided_company_name="user_provided_company_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -107,7 +107,7 @@ async def test_raw_response_activate_account(self, async_client: AsyncCodex) -> assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -120,6 +120,6 @@ async def test_streaming_response_activate_account(self, async_client: AsyncCode assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index a58477bc..c6ed0209 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -8,9 +8,8 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import User from tests.utils import assert_matches_type -from codex.types.users import UserSchema +from codex.types.users import UserSchema, UserSchemaPublic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +21,7 @@ class TestAPIKey: @parametrize def test_method_retrieve(self, client: Codex) -> None: api_key = client.users.myself.api_key.retrieve() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -32,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -42,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) assert cast(Any, response.is_closed) is True @@ -82,7 +81,7 @@ class TestAsyncAPIKey: @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: api_key = await async_client.users.myself.api_key.retrieve() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -92,7 +91,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = await response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -102,7 +101,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = await response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index 174f2cec..63123275 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -8,8 +8,8 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import User from tests.utils import assert_matches_type +from codex.types.users import UserSchemaPublic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,7 +21,7 @@ class TestMyself: @parametrize def test_method_retrieve(self, client: Codex) -> None: myself = client.users.myself.retrieve() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -31,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -41,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) assert cast(Any, response.is_closed) is True @@ -53,7 +53,7 @@ class TestAsyncMyself: @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: myself = await async_client.users.myself.retrieve() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -63,7 +63,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = await response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -73,6 +73,6 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = await response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) assert cast(Any, response.is_closed) is True From 4299a96385535d570406f5930797ba47e5d53302 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:59:42 +0000 Subject: [PATCH 109/320] feat(api): add project increment_queries and other recent endpoints --- .stats.yml | 2 +- src/codex/_client.py | 71 ------------------- src/codex/resources/projects/access_keys.py | 4 ++ src/codex/resources/projects/clusters.py | 4 +- src/codex/resources/projects/entries.py | 12 +++- src/codex/resources/projects/projects.py | 8 +-- src/codex/types/project_list_params.py | 6 +- .../projects/access_key_create_params.py | 2 + .../types/projects/entry_create_params.py | 2 + .../types/projects/entry_query_params.py | 2 + .../projects/test_access_keys.py | 2 + tests/api_resources/projects/test_entries.py | 4 ++ tests/api_resources/test_projects.py | 28 +++----- 13 files changed, 44 insertions(+), 103 deletions(-) diff --git a/.stats.yml b/.stats.yml index aa1535bd..fd7bea9a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d +openapi_spec_hash: 8d2e7726c60bca0dcfc72b1e2df34ef1 config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/src/codex/_client.py b/src/codex/_client.py index 2641513f..f035421c 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -13,7 +13,6 @@ from ._types import ( NOT_GIVEN, Omit, - Headers, Timeout, NotGiven, Transport, @@ -151,25 +150,6 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - return {**self._authenticated_api_key, **self._public_access_key} - - @property - def _authenticated_api_key(self) -> dict[str, str]: - api_key = self.api_key - if api_key is None: - return {} - return {"X-API-Key": api_key} - - @property - def _public_access_key(self) -> dict[str, str]: - access_key = self.access_key - if access_key is None: - return {} - return {"X-Access-Key": access_key} - @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -179,22 +159,6 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } - @override - def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.api_key and headers.get("X-API-Key"): - return - if isinstance(custom_headers.get("X-API-Key"), Omit): - return - - if self.access_key and headers.get("X-Access-Key"): - return - if isinstance(custom_headers.get("X-Access-Key"), Omit): - return - - raise TypeError( - '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' - ) - def copy( self, *, @@ -379,25 +343,6 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - return {**self._authenticated_api_key, **self._public_access_key} - - @property - def _authenticated_api_key(self) -> dict[str, str]: - api_key = self.api_key - if api_key is None: - return {} - return {"X-API-Key": api_key} - - @property - def _public_access_key(self) -> dict[str, str]: - access_key = self.access_key - if access_key is None: - return {} - return {"X-Access-Key": access_key} - @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -407,22 +352,6 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } - @override - def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.api_key and headers.get("X-API-Key"): - return - if isinstance(custom_headers.get("X-API-Key"), Omit): - return - - if self.access_key and headers.get("X-Access-Key"): - return - if isinstance(custom_headers.get("X-Access-Key"), Omit): - return - - raise TypeError( - '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' - ) - def copy( self, *, diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 61987399..9ba2e131 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -57,6 +57,7 @@ def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -85,6 +86,7 @@ def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -346,6 +348,7 @@ async def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -374,6 +377,7 @@ async def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index 584cde18..eabbe60e 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -115,7 +115,7 @@ def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants Route + Get Cluster Variants Args: extra_headers: Send extra headers @@ -230,7 +230,7 @@ async def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants Route + Get Cluster Variants Args: extra_headers: Send extra headers diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index f88e2243..bdfee06d 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -57,6 +57,7 @@ def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -85,6 +86,7 @@ def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -323,6 +325,7 @@ def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -335,7 +338,7 @@ def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries Route + Query Entries Args: extra_headers: Send extra headers @@ -351,6 +354,7 @@ def query( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -446,6 +450,7 @@ async def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -474,6 +479,7 @@ async def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -712,6 +718,7 @@ async def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -724,7 +731,7 @@ async def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries Route + Query Entries Args: extra_headers: Send extra headers @@ -740,6 +747,7 @@ async def query( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 47639a5a..76237fb6 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -208,11 +208,11 @@ def update( def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -243,11 +243,11 @@ def list( timeout=timeout, query=maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, @@ -513,11 +513,11 @@ async def update( async def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -548,11 +548,11 @@ async def list( timeout=timeout, query=await async_maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index c2589598..0ab3b84b 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -3,14 +3,12 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, TypedDict __all__ = ["ProjectListParams"] class ProjectListParams(TypedDict, total=False): - organization_id: Required[str] - include_entry_counts: bool limit: int @@ -19,6 +17,8 @@ class ProjectListParams(TypedDict, total=False): order: Literal["asc", "desc"] + organization_id: str + query: Optional[str] sort: Literal["created_at", "updated_at"] diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py index cf5f00fb..5035836d 100644 --- a/src/codex/types/projects/access_key_create_params.py +++ b/src/codex/types/projects/access_key_create_params.py @@ -18,6 +18,8 @@ class AccessKeyCreateParams(TypedDict, total=False): expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py index f06846bb..490b0e83 100644 --- a/src/codex/types/projects/entry_create_params.py +++ b/src/codex/types/projects/entry_create_params.py @@ -19,6 +19,8 @@ class EntryCreateParams(TypedDict, total=False): draft_answer: Optional[str] + x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index d58b7bfa..db1aec77 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -17,6 +17,8 @@ class EntryQueryParams(TypedDict, total=False): client_metadata: Optional[object] + x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index ad4ee5e4..bcf3bb7a 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -399,6 +400,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 31a5e408..710d2146 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -396,6 +397,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None: question="question", use_llm_matching=True, client_metadata={}, + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -514,6 +516,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -871,6 +874,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N question="question", use_llm_matching=True, client_metadata={}, + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index bfa657d2..d71d0e19 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -194,20 +194,18 @@ def test_path_params_update(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: - project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -216,9 +214,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -228,9 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + with client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -542,20 +536,18 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = await async_client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -564,9 +556,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = await async_client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -576,9 +566,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + async with async_client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From a2ef2b1418d9a7f394cce2151ee7cb6ad084af45 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 18:17:09 +0000 Subject: [PATCH 110/320] feat(api): api update --- .stats.yml | 2 +- src/codex/_client.py | 71 +++++++++++++++++++ src/codex/resources/projects/access_keys.py | 4 -- src/codex/resources/projects/clusters.py | 4 +- src/codex/resources/projects/entries.py | 12 +--- src/codex/resources/projects/projects.py | 8 +-- src/codex/types/project_list_params.py | 6 +- .../projects/access_key_create_params.py | 2 - .../types/projects/entry_create_params.py | 2 - .../types/projects/entry_query_params.py | 2 - .../projects/test_access_keys.py | 2 - tests/api_resources/projects/test_entries.py | 4 -- tests/api_resources/test_projects.py | 28 +++++--- 13 files changed, 103 insertions(+), 44 deletions(-) diff --git a/.stats.yml b/.stats.yml index fd7bea9a..aa1535bd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 8d2e7726c60bca0dcfc72b1e2df34ef1 +openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/src/codex/_client.py b/src/codex/_client.py index f035421c..2641513f 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -13,6 +13,7 @@ from ._types import ( NOT_GIVEN, Omit, + Headers, Timeout, NotGiven, Transport, @@ -150,6 +151,25 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + return {**self._authenticated_api_key, **self._public_access_key} + + @property + def _authenticated_api_key(self) -> dict[str, str]: + api_key = self.api_key + if api_key is None: + return {} + return {"X-API-Key": api_key} + + @property + def _public_access_key(self) -> dict[str, str]: + access_key = self.access_key + if access_key is None: + return {} + return {"X-Access-Key": access_key} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -159,6 +179,22 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.api_key and headers.get("X-API-Key"): + return + if isinstance(custom_headers.get("X-API-Key"), Omit): + return + + if self.access_key and headers.get("X-Access-Key"): + return + if isinstance(custom_headers.get("X-Access-Key"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + ) + def copy( self, *, @@ -343,6 +379,25 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + return {**self._authenticated_api_key, **self._public_access_key} + + @property + def _authenticated_api_key(self) -> dict[str, str]: + api_key = self.api_key + if api_key is None: + return {} + return {"X-API-Key": api_key} + + @property + def _public_access_key(self) -> dict[str, str]: + access_key = self.access_key + if access_key is None: + return {} + return {"X-Access-Key": access_key} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -352,6 +407,22 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.api_key and headers.get("X-API-Key"): + return + if isinstance(custom_headers.get("X-API-Key"), Omit): + return + + if self.access_key and headers.get("X-Access-Key"): + return + if isinstance(custom_headers.get("X-Access-Key"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + ) + def copy( self, *, diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 9ba2e131..61987399 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -57,7 +57,6 @@ def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -86,7 +85,6 @@ def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -348,7 +346,6 @@ async def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -377,7 +374,6 @@ async def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index eabbe60e..584cde18 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -115,7 +115,7 @@ def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants + Get Cluster Variants Route Args: extra_headers: Send extra headers @@ -230,7 +230,7 @@ async def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants + Get Cluster Variants Route Args: extra_headers: Send extra headers diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index bdfee06d..f88e2243 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -57,7 +57,6 @@ def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -86,7 +85,6 @@ def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -325,7 +323,6 @@ def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -338,7 +335,7 @@ def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries + Query Entries Route Args: extra_headers: Send extra headers @@ -354,7 +351,6 @@ def query( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -450,7 +446,6 @@ async def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -479,7 +474,6 @@ async def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -718,7 +712,6 @@ async def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -731,7 +724,7 @@ async def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries + Query Entries Route Args: extra_headers: Send extra headers @@ -747,7 +740,6 @@ async def query( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 76237fb6..47639a5a 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -208,11 +208,11 @@ def update( def list( self, *, + organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -243,11 +243,11 @@ def list( timeout=timeout, query=maybe_transform( { + "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, - "organization_id": organization_id, "query": query, "sort": sort, }, @@ -513,11 +513,11 @@ async def update( async def list( self, *, + organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -548,11 +548,11 @@ async def list( timeout=timeout, query=await async_maybe_transform( { + "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, - "organization_id": organization_id, "query": query, "sort": sort, }, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index 0ab3b84b..c2589598 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -3,12 +3,14 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["ProjectListParams"] class ProjectListParams(TypedDict, total=False): + organization_id: Required[str] + include_entry_counts: bool limit: int @@ -17,8 +19,6 @@ class ProjectListParams(TypedDict, total=False): order: Literal["asc", "desc"] - organization_id: str - query: Optional[str] sort: Literal["created_at", "updated_at"] diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py index 5035836d..cf5f00fb 100644 --- a/src/codex/types/projects/access_key_create_params.py +++ b/src/codex/types/projects/access_key_create_params.py @@ -18,8 +18,6 @@ class AccessKeyCreateParams(TypedDict, total=False): expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py index 490b0e83..f06846bb 100644 --- a/src/codex/types/projects/entry_create_params.py +++ b/src/codex/types/projects/entry_create_params.py @@ -19,8 +19,6 @@ class EntryCreateParams(TypedDict, total=False): draft_answer: Optional[str] - x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index db1aec77..d58b7bfa 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -17,8 +17,6 @@ class EntryQueryParams(TypedDict, total=False): client_metadata: Optional[object] - x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index bcf3bb7a..ad4ee5e4 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -39,7 +39,6 @@ def test_method_create_with_all_params(self, client: Codex) -> None: name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -400,7 +399,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 710d2146..31a5e408 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -39,7 +39,6 @@ def test_method_create_with_all_params(self, client: Codex) -> None: answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -397,7 +396,6 @@ def test_method_query_with_all_params(self, client: Codex) -> None: question="question", use_llm_matching=True, client_metadata={}, - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -516,7 +514,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -874,7 +871,6 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N question="question", use_llm_matching=True, client_metadata={}, - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index d71d0e19..bfa657d2 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -194,18 +194,20 @@ def test_path_params_update(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: - project = client.projects.list() + project = client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -214,7 +216,9 @@ def test_method_list_with_all_params(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.with_raw_response.list() + response = client.projects.with_raw_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -224,7 +228,9 @@ def test_raw_response_list(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.with_streaming_response.list() as response: + with client.projects.with_streaming_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -536,18 +542,20 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.list() + project = await async_client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -556,7 +564,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.with_raw_response.list() + response = await async_client.projects.with_raw_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -566,7 +576,9 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.with_streaming_response.list() as response: + async with async_client.projects.with_streaming_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 30952e1d34a979f5de4e4ee612e0d5eb11443bbb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:16:17 +0000 Subject: [PATCH 111/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 8 +++---- src/codex/types/project_list_params.py | 6 ++--- tests/api_resources/test_projects.py | 28 +++++++----------------- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/.stats.yml b/.stats.yml index aa1535bd..42e47500 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d +openapi_spec_hash: 684572da9b97ec2c9acf3ea698c7ce12 config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 47639a5a..76237fb6 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -208,11 +208,11 @@ def update( def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -243,11 +243,11 @@ def list( timeout=timeout, query=maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, @@ -513,11 +513,11 @@ async def update( async def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -548,11 +548,11 @@ async def list( timeout=timeout, query=await async_maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index c2589598..0ab3b84b 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -3,14 +3,12 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, TypedDict __all__ = ["ProjectListParams"] class ProjectListParams(TypedDict, total=False): - organization_id: Required[str] - include_entry_counts: bool limit: int @@ -19,6 +17,8 @@ class ProjectListParams(TypedDict, total=False): order: Literal["asc", "desc"] + organization_id: str + query: Optional[str] sort: Literal["created_at", "updated_at"] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index bfa657d2..d71d0e19 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -194,20 +194,18 @@ def test_path_params_update(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: - project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -216,9 +214,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -228,9 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + with client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -542,20 +536,18 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = await async_client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -564,9 +556,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = await async_client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -576,9 +566,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + async with async_client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 419f26bfb8c9acd7c45d6881dbed365d07146ae6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:25:10 +0000 Subject: [PATCH 112/320] chore(ci): add timeout thresholds for CI jobs --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8b72361..1e4dab9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: jobs: lint: + timeout-minutes: 10 name: lint runs-on: ubuntu-latest steps: From 825b36271a9985b00ebaab7b37608d8b09984f75 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:25:42 +0000 Subject: [PATCH 113/320] chore(internal): import reformatting --- src/codex/_client.py | 5 +---- src/codex/resources/projects/access_keys.py | 6 +----- src/codex/resources/projects/entries.py | 6 +----- src/codex/resources/projects/projects.py | 5 +---- src/codex/resources/tlm.py | 5 +---- src/codex/resources/users/users.py | 5 +---- 6 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index 2641513f..7464f6dc 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -20,10 +20,7 @@ ProxiesTypes, RequestOptions, ) -from ._utils import ( - is_given, - get_async_library, -) +from ._utils import is_given, get_async_library from ._version import __version__ from .resources import tlm, health from ._streaming import Stream as Stream, AsyncStream as AsyncStream diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 61987399..15190030 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -8,11 +8,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - strip_not_given, - async_maybe_transform, -) +from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index f88e2243..a9e690b9 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -7,11 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - strip_not_given, - async_maybe_transform, -) +from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 76237fb6..95bcd3a3 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -17,10 +17,7 @@ AsyncEntriesResourceWithStreamingResponse, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from .clusters import ( ClustersResource, AsyncClustersResource, diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index c6585d0d..78f97e2e 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -9,10 +9,7 @@ from ..types import tlm_score_params, tlm_prompt_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index c276f950..d207a96d 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -9,10 +9,7 @@ from ...types import user_activate_account_params from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( From cb23e13617963afa0864b3a176b82111e939cb32 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:27:01 +0000 Subject: [PATCH 114/320] chore(internal): fix list file params --- src/codex/_utils/_utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py index e5811bba..ea3cf3f2 100644 --- a/src/codex/_utils/_utils.py +++ b/src/codex/_utils/_utils.py @@ -72,8 +72,16 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for entry in obj: + assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") + files.append((flattened_key + "[]", cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 From daaace047284e762fd8a4d90f3c5c3aed6678e78 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:27:27 +0000 Subject: [PATCH 115/320] chore(internal): refactor retries to not use recursion --- src/codex/_base_client.py | 414 ++++++++++++++++---------------------- 1 file changed, 175 insertions(+), 239 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 69be76db..0bf6ca7f 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -437,8 +437,7 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - options.idempotency_key = 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 @@ -903,7 +902,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -914,7 +912,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -924,7 +921,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, @@ -934,125 +930,109 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - retries_taken: int, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) - - cast_to = self._maybe_override_cast_to(cast_to, options) - options = self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - self._prepare_request(request) - - if options.idempotency_key: + 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 = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return self._process_response( cast_to=cast_to, options=options, @@ -1062,37 +1042,20 @@ def _request( retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) - # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a - # different thread if necessary. time.sleep(timeout) - return self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1436,7 +1399,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1447,7 +1409,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1458,7 +1419,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1468,120 +1428,111 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - retries_taken: int, ) -> ResponseT | _AsyncStreamT: if self._platform is None: # `get_platform` can make blocking IO calls so we # execute it earlier while we are in an async context self._platform = await asyncify(get_platform)() + cast_to = self._maybe_override_cast_to(cast_to, options) + # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) - - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - await self._prepare_request(request) - - if options.idempotency_key: + 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 = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - 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) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + 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 remaining_retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # 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, @@ -1591,35 +1542,20 @@ async def _request( retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) await anyio.sleep(timeout) - return await self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, From 0e5083c23afb29fded40fa35d71938bb9a3edf80 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:28:00 +0000 Subject: [PATCH 116/320] fix(pydantic v1): more robust ModelField.annotation check --- src/codex/_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index 58b9263e..798956f1 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -626,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, # Note: if one variant defines an alias then they all should discriminator_alias = field_info.alias - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant From a1469f82a18840cddf5cd343a434276119c2dff2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:16:17 +0000 Subject: [PATCH 117/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 42e47500..185dd58f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 684572da9b97ec2c9acf3ea698c7ce12 +openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7 config_hash: 2d88a0a41f5faca603ff2789a116d988 From 13f2a9da6a90261442fc164749acc7bd47cf5de3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:53:25 +0000 Subject: [PATCH 118/320] feat(api): add proj analytics endpoint --- .stats.yml | 4 +- api.md | 2 + src/codex/resources/projects/projects.py | 126 +++++++++++++++++- src/codex/types/__init__.py | 2 + .../project_retrieve_analytics_params.py | 18 +++ .../project_retrieve_analytics_response.py | 43 ++++++ tests/api_resources/test_projects.py | 107 +++++++++++++++ 7 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 src/codex/types/project_retrieve_analytics_params.py create mode 100644 src/codex/types/project_retrieve_analytics_response.py diff --git a/.stats.yml b/.stats.yml index 185dd58f..230d2377 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 42 +configured_endpoints: 43 openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7 -config_hash: 2d88a0a41f5faca603ff2789a116d988 +config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/api.md b/api.md index 617cdd24..f240632f 100644 --- a/api.md +++ b/api.md @@ -141,6 +141,7 @@ from codex.types import ( ProjectListResponse, ProjectExportResponse, ProjectIncrementQueriesResponse, + ProjectRetrieveAnalyticsResponse, ) ``` @@ -153,6 +154,7 @@ Methods: - client.projects.delete(project_id) -> None - client.projects.export(project_id) -> object - client.projects.increment_queries(project_id) -> object +- client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse ## AccessKeys diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 95bcd3a3..6ac036c0 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -7,7 +7,12 @@ import httpx -from ...types import project_list_params, project_create_params, project_update_params +from ...types import ( + project_list_params, + project_create_params, + project_update_params, + project_retrieve_analytics_params, +) from .entries import ( EntriesResource, AsyncEntriesResource, @@ -46,6 +51,7 @@ from ...types.project_list_response import ProjectListResponse from ...types.project_return_schema import ProjectReturnSchema from ...types.project_retrieve_response import ProjectRetrieveResponse +from ...types.project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -354,6 +360,59 @@ def increment_queries( cast_to=object, ) + def retrieve_analytics( + self, + project_id: str, + *, + end: int | NotGiven = NOT_GIVEN, + sme_limit: int | NotGiven = NOT_GIVEN, + start: int | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectRetrieveAnalyticsResponse: + """ + Get Project Analytics Route + + Args: + end: End timestamp in seconds since epoch + + sme_limit: Limit the number of top SME contributors to return. + + start: Start timestamp in seconds since epoch + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + f"/api/projects/{project_id}/analytics/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "end": end, + "sme_limit": sme_limit, + "start": start, + }, + project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, + ), + ), + cast_to=ProjectRetrieveAnalyticsResponse, + ) + class AsyncProjectsResource(AsyncAPIResource): @cached_property @@ -659,6 +718,59 @@ async def increment_queries( cast_to=object, ) + async def retrieve_analytics( + self, + project_id: str, + *, + end: int | NotGiven = NOT_GIVEN, + sme_limit: int | NotGiven = NOT_GIVEN, + start: int | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectRetrieveAnalyticsResponse: + """ + Get Project Analytics Route + + Args: + end: End timestamp in seconds since epoch + + sme_limit: Limit the number of top SME contributors to return. + + start: Start timestamp in seconds since epoch + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + f"/api/projects/{project_id}/analytics/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "end": end, + "sme_limit": sme_limit, + "start": start, + }, + project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, + ), + ), + cast_to=ProjectRetrieveAnalyticsResponse, + ) + class ProjectsResourceWithRawResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -685,6 +797,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.increment_queries = to_raw_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = to_raw_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithRawResponse: @@ -724,6 +839,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.increment_queries = async_to_raw_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = async_to_raw_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: @@ -763,6 +881,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.increment_queries = to_streamed_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = to_streamed_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithStreamingResponse: @@ -802,6 +923,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.increment_queries = async_to_streamed_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = async_to_streamed_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 008c4a67..53d1ab6b 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -15,7 +15,9 @@ from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams +from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse +from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse from .organization_retrieve_permissions_response import ( OrganizationRetrievePermissionsResponse as OrganizationRetrievePermissionsResponse, ) diff --git a/src/codex/types/project_retrieve_analytics_params.py b/src/codex/types/project_retrieve_analytics_params.py new file mode 100644 index 00000000..aadb4159 --- /dev/null +++ b/src/codex/types/project_retrieve_analytics_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ProjectRetrieveAnalyticsParams"] + + +class ProjectRetrieveAnalyticsParams(TypedDict, total=False): + end: int + """End timestamp in seconds since epoch""" + + sme_limit: int + """Limit the number of top SME contributors to return.""" + + start: int + """Start timestamp in seconds since epoch""" diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py new file mode 100644 index 00000000..f17975ec --- /dev/null +++ b/src/codex/types/project_retrieve_analytics_response.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List + +from .._models import BaseModel + +__all__ = [ + "ProjectRetrieveAnalyticsResponse", + "AnswersPublished", + "AnswersPublishedAnswersByAuthor", + "BadResponses", + "Queries", +] + + +class AnswersPublishedAnswersByAuthor(BaseModel): + answers_published: int + + name: str + + user_id: str + + +class AnswersPublished(BaseModel): + answers_by_author: List[AnswersPublishedAnswersByAuthor] + + +class BadResponses(BaseModel): + responses_by_type: Dict[str, int] + + total: int + + +class Queries(BaseModel): + total: int + + +class ProjectRetrieveAnalyticsResponse(BaseModel): + answers_published: AnswersPublished + + bad_responses: BadResponses + + queries: Queries diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index d71d0e19..3a8f0ec8 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -12,6 +12,7 @@ ProjectListResponse, ProjectReturnSchema, ProjectRetrieveResponse, + ProjectRetrieveAnalyticsResponse, ) from tests.utils import assert_matches_type @@ -359,6 +360,59 @@ def test_path_params_increment_queries(self, client: Codex) -> None: "", ) + @pytest.mark.skip() + @parametrize + def test_method_retrieve_analytics(self, client: Codex) -> None: + project = client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None: + project = client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=0, + sme_limit=1, + start=0, + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve_analytics(self, client: Codex) -> None: + response = client.projects.with_raw_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve_analytics(self, client: Codex) -> None: + with client.projects.with_streaming_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve_analytics(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.retrieve_analytics( + project_id="", + ) + class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -700,3 +754,56 @@ async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> await async_client.projects.with_raw_response.increment_queries( "", ) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_analytics_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=0, + sme_limit=1, + start=0, + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve_analytics(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve_analytics(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.retrieve_analytics( + project_id="", + ) From 92c72a790a8b0cab55e3b3a8e19f2c0cd0aed005 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:08:50 +0000 Subject: [PATCH 119/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7e56fe29..e2f2c074 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.16" + ".": "0.1.0-alpha.17" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3b48a5bb..24f75152 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.16" +version = "0.1.0-alpha.17" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index b99310c3..264d49e0 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.16" # x-release-please-version +__version__ = "0.1.0-alpha.17" # x-release-please-version From d96e28e8ff2fd2b72fb5121c4cb814cfa21e15ee Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 20:16:28 +0000 Subject: [PATCH 120/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 8 -------- src/codex/types/project_retrieve_analytics_params.py | 3 --- src/codex/types/project_retrieve_analytics_response.py | 9 ++++++++- tests/api_resources/test_projects.py | 2 -- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.stats.yml b/.stats.yml index 230d2377..9e3b9924 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 43 -openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7 +openapi_spec_hash: 1d2bab7a30685ae6ba2f0a98abf2dd54 config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 6ac036c0..bd50a684 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -365,7 +365,6 @@ def retrieve_analytics( project_id: str, *, end: int | NotGiven = NOT_GIVEN, - sme_limit: int | NotGiven = NOT_GIVEN, start: int | NotGiven = NOT_GIVEN, # 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. @@ -380,8 +379,6 @@ def retrieve_analytics( Args: end: End timestamp in seconds since epoch - sme_limit: Limit the number of top SME contributors to return. - start: Start timestamp in seconds since epoch extra_headers: Send extra headers @@ -404,7 +401,6 @@ def retrieve_analytics( query=maybe_transform( { "end": end, - "sme_limit": sme_limit, "start": start, }, project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, @@ -723,7 +719,6 @@ async def retrieve_analytics( project_id: str, *, end: int | NotGiven = NOT_GIVEN, - sme_limit: int | NotGiven = NOT_GIVEN, start: int | NotGiven = NOT_GIVEN, # 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. @@ -738,8 +733,6 @@ async def retrieve_analytics( Args: end: End timestamp in seconds since epoch - sme_limit: Limit the number of top SME contributors to return. - start: Start timestamp in seconds since epoch extra_headers: Send extra headers @@ -762,7 +755,6 @@ async def retrieve_analytics( query=await async_maybe_transform( { "end": end, - "sme_limit": sme_limit, "start": start, }, project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, diff --git a/src/codex/types/project_retrieve_analytics_params.py b/src/codex/types/project_retrieve_analytics_params.py index aadb4159..c5f9a48c 100644 --- a/src/codex/types/project_retrieve_analytics_params.py +++ b/src/codex/types/project_retrieve_analytics_params.py @@ -11,8 +11,5 @@ class ProjectRetrieveAnalyticsParams(TypedDict, total=False): end: int """End timestamp in seconds since epoch""" - sme_limit: int - """Limit the number of top SME contributors to return.""" - start: int """Start timestamp in seconds since epoch""" diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py index f17975ec..dce14505 100644 --- a/src/codex/types/project_retrieve_analytics_response.py +++ b/src/codex/types/project_retrieve_analytics_response.py @@ -9,6 +9,7 @@ "AnswersPublished", "AnswersPublishedAnswersByAuthor", "BadResponses", + "BadResponsesResponsesByType", "Queries", ] @@ -25,8 +26,14 @@ class AnswersPublished(BaseModel): answers_by_author: List[AnswersPublishedAnswersByAuthor] +class BadResponsesResponsesByType(BaseModel): + num_prevented: int + + total: int + + class BadResponses(BaseModel): - responses_by_type: Dict[str, int] + responses_by_type: Dict[str, BadResponsesResponsesByType] total: int diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 3a8f0ec8..772a7b29 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -374,7 +374,6 @@ def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None: project = client.projects.retrieve_analytics( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=0, - sme_limit=1, start=0, ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) @@ -769,7 +768,6 @@ async def test_method_retrieve_analytics_with_all_params(self, async_client: Asy project = await async_client.projects.retrieve_analytics( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=0, - sme_limit=1, start=0, ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) From e255acd5c4777cb488aa63b7599d1a3f34459018 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:12:17 +0000 Subject: [PATCH 121/320] chore(internal): minor formatting changes --- src/codex/types/health_check_response.py | 1 - .../organizations/billing/organization_billing_setup_intent.py | 1 - .../types/organizations/organization_billing_invoices_schema.py | 1 - .../types/organizations/organization_billing_usage_schema.py | 1 - .../types/projects/access_key_retrieve_project_id_response.py | 1 - src/codex/types/projects/entry_notify_sme_response.py | 1 - 6 files changed, 6 deletions(-) diff --git a/src/codex/types/health_check_response.py b/src/codex/types/health_check_response.py index 7a2655d3..b3b7958a 100644 --- a/src/codex/types/health_check_response.py +++ b/src/codex/types/health_check_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["HealthCheckResponse"] diff --git a/src/codex/types/organizations/billing/organization_billing_setup_intent.py b/src/codex/types/organizations/billing/organization_billing_setup_intent.py index dfd07d6b..25b3f553 100644 --- a/src/codex/types/organizations/billing/organization_billing_setup_intent.py +++ b/src/codex/types/organizations/billing/organization_billing_setup_intent.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ...._models import BaseModel __all__ = ["OrganizationBillingSetupIntent"] diff --git a/src/codex/types/organizations/organization_billing_invoices_schema.py b/src/codex/types/organizations/organization_billing_invoices_schema.py index e588ded0..64b2959a 100644 --- a/src/codex/types/organizations/organization_billing_invoices_schema.py +++ b/src/codex/types/organizations/organization_billing_invoices_schema.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["OrganizationBillingInvoicesSchema"] diff --git a/src/codex/types/organizations/organization_billing_usage_schema.py b/src/codex/types/organizations/organization_billing_usage_schema.py index 8369fb31..05129502 100644 --- a/src/codex/types/organizations/organization_billing_usage_schema.py +++ b/src/codex/types/organizations/organization_billing_usage_schema.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["OrganizationBillingUsageSchema"] diff --git a/src/codex/types/projects/access_key_retrieve_project_id_response.py b/src/codex/types/projects/access_key_retrieve_project_id_response.py index 22775083..e204ccbc 100644 --- a/src/codex/types/projects/access_key_retrieve_project_id_response.py +++ b/src/codex/types/projects/access_key_retrieve_project_id_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["AccessKeyRetrieveProjectIDResponse"] diff --git a/src/codex/types/projects/entry_notify_sme_response.py b/src/codex/types/projects/entry_notify_sme_response.py index dd05a6cf..b3c5b373 100644 --- a/src/codex/types/projects/entry_notify_sme_response.py +++ b/src/codex/types/projects/entry_notify_sme_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["EntryNotifySmeResponse"] From a3af8c3cb52de0a24a89f7908597d430381051bb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:12:45 +0000 Subject: [PATCH 122/320] chore(internal): codegen related update --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e4dab9d..cab1a968 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,18 @@ name: CI on: push: - branches: - - main - pull_request: - branches: - - main - - next + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: timeout-minutes: 10 name: lint - runs-on: ubuntu-latest + runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 7c078aa3..6ae114ba 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -11,7 +11,7 @@ on: jobs: publish: name: publish - runs-on: ubuntu-latest + runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 2ce3cdc2..1c7b27dc 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -8,7 +8,7 @@ on: jobs: release_doctor: name: release doctor - runs-on: ubuntu-latest + runs-on: depot-ubuntu-24.04 if: github.repository == 'cleanlab/codex-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: From 0bf5b7c001b32b7f446fe72a3d36cdae877758f0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:13:10 +0000 Subject: [PATCH 123/320] chore(ci): only use depot for staging repos --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cab1a968..fc62f784 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: depot-ubuntu-24.04 + runs-on: ${{ github.repository == 'stainless-sdks/codex-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 6ae114ba..7c078aa3 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -11,7 +11,7 @@ on: jobs: publish: name: publish - runs-on: depot-ubuntu-24.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 1c7b27dc..2ce3cdc2 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -8,7 +8,7 @@ on: jobs: release_doctor: name: release doctor - runs-on: depot-ubuntu-24.04 + runs-on: ubuntu-latest if: github.repository == 'cleanlab/codex-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: From e6a7b74993a02b7ced3bbb10a44a44f59a68bcca Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:14:20 +0000 Subject: [PATCH 124/320] chore: broadly detect json family of content-type headers --- src/codex/_response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_response.py b/src/codex/_response.py index 6e7c1cdf..e4ddf703 100644 --- a/src/codex/_response.py +++ b/src/codex/_response.py @@ -233,7 +233,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() From 32906a582d4be4c659d4d6cb183e1aa9218f4c73 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:16:27 +0000 Subject: [PATCH 125/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/clusters.py | 28 +++++++++++- .../project_retrieve_analytics_response.py | 2 + .../types/projects/cluster_list_params.py | 14 +++++- .../types/projects/cluster_list_response.py | 44 +++++++++++++++++++ src/codex/types/projects/entry.py | 44 +++++++++++++++++++ .../types/projects/entry_query_response.py | 40 +++++++++++++++++ tests/api_resources/projects/test_clusters.py | 2 + 8 files changed, 172 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9e3b9924..7a2f56a6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 43 -openapi_spec_hash: 1d2bab7a30685ae6ba2f0a98abf2dd54 +openapi_spec_hash: b7beefbd38b4fcdd191cdb81a18a023b config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index 584cde18..fea7e28e 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -52,10 +52,21 @@ def list( *, eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] | NotGiven = NOT_GIVEN, + instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]] + sort: Optional[ + Literal[ + "created_at", + "answered_at", + "cluster_frequency_count", + "custom_rank", + "eval_score", + "html_format_score", + "content_structure_score", + ] + ] | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -90,6 +101,7 @@ def list( query=maybe_transform( { "eval_issue_types": eval_issue_types, + "instruction_adherence_failure": instruction_adherence_failure, "limit": limit, "offset": offset, "order": order, @@ -167,10 +179,21 @@ def list( *, eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] | NotGiven = NOT_GIVEN, + instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]] + sort: Optional[ + Literal[ + "created_at", + "answered_at", + "cluster_frequency_count", + "custom_rank", + "eval_score", + "html_format_score", + "content_structure_score", + ] + ] | NotGiven = NOT_GIVEN, states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -205,6 +228,7 @@ def list( query=maybe_transform( { "eval_issue_types": eval_issue_types, + "instruction_adherence_failure": instruction_adherence_failure, "limit": limit, "offset": offset, "order": order, diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py index dce14505..b1e5d85e 100644 --- a/src/codex/types/project_retrieve_analytics_response.py +++ b/src/codex/types/project_retrieve_analytics_response.py @@ -17,6 +17,8 @@ class AnswersPublishedAnswersByAuthor(BaseModel): answers_published: int + email: str + name: str user_id: str diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py index fa84a6b0..cff2b85e 100644 --- a/src/codex/types/projects/cluster_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -11,12 +11,24 @@ class ClusterListParams(TypedDict, total=False): eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] + limit: int offset: int order: Literal["asc", "desc"] - sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank", "eval_score"]] + sort: Optional[ + Literal[ + "created_at", + "answered_at", + "cluster_frequency_count", + "custom_rank", + "eval_score", + "html_format_score", + "content_structure_score", + ] + ] states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py index c149fbdb..2e8b5426 100644 --- a/src/codex/types/projects/cluster_list_response.py +++ b/src/codex/types/projects/cluster_list_response.py @@ -9,13 +9,31 @@ __all__ = [ "ClusterListResponse", "ManagedMetadata", + "ManagedMetadataContentStructureScores", "ManagedMetadataContextSufficiency", + "ManagedMetadataHTMLFormatScores", "ManagedMetadataQueryEaseCustomized", "ManagedMetadataResponseHelpfulness", "ManagedMetadataTrustworthiness", ] +class ManagedMetadataContentStructureScores(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class ManagedMetadataContextSufficiency(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -32,6 +50,22 @@ class ManagedMetadataContextSufficiency(BaseModel): scores: Optional[List[float]] = None +class ManagedMetadataHTMLFormatScores(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class ManagedMetadataQueryEaseCustomized(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -93,6 +127,9 @@ class ManagedMetadata(BaseModel): latest_location: Optional[str] = None """The most recent location string.""" + content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None + """Holds a list of scores and computes aggregate statistics.""" + context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None """Holds a list of scores and computes aggregate statistics.""" @@ -100,6 +137,9 @@ class ManagedMetadata(BaseModel): entry_points: Optional[List[str]] = None + html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None + """Holds a list of scores and computes aggregate statistics.""" + llm_responses: Optional[List[str]] = None locations: Optional[List[str]] = None @@ -136,6 +176,8 @@ class ClusterListResponse(BaseModel): client_query_metadata: Optional[List[object]] = None + content_structure_score: Optional[float] = None + draft_answer: Optional[str] = None draft_answer_last_edited: Optional[datetime] = None @@ -147,4 +189,6 @@ class ClusterListResponse(BaseModel): frequency_count: Optional[int] = None """number of times the entry matched for a /query request""" + html_format_score: Optional[float] = None + representative_entry_id: Optional[str] = None diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index 638fe352..eb2a2217 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -9,13 +9,31 @@ __all__ = [ "Entry", "ManagedMetadata", + "ManagedMetadataContentStructureScores", "ManagedMetadataContextSufficiency", + "ManagedMetadataHTMLFormatScores", "ManagedMetadataQueryEaseCustomized", "ManagedMetadataResponseHelpfulness", "ManagedMetadataTrustworthiness", ] +class ManagedMetadataContentStructureScores(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class ManagedMetadataContextSufficiency(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -32,6 +50,22 @@ class ManagedMetadataContextSufficiency(BaseModel): scores: Optional[List[float]] = None +class ManagedMetadataHTMLFormatScores(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class ManagedMetadataQueryEaseCustomized(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -93,6 +127,9 @@ class ManagedMetadata(BaseModel): latest_location: Optional[str] = None """The most recent location string.""" + content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None + """Holds a list of scores and computes aggregate statistics.""" + context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None """Holds a list of scores and computes aggregate statistics.""" @@ -100,6 +137,9 @@ class ManagedMetadata(BaseModel): entry_points: Optional[List[str]] = None + html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None + """Holds a list of scores and computes aggregate statistics.""" + llm_responses: Optional[List[str]] = None locations: Optional[List[str]] = None @@ -134,6 +174,8 @@ class Entry(BaseModel): client_query_metadata: Optional[List[object]] = None + content_structure_score: Optional[float] = None + draft_answer: Optional[str] = None draft_answer_last_edited: Optional[datetime] = None @@ -144,3 +186,5 @@ class Entry(BaseModel): frequency_count: Optional[int] = None """number of times the entry matched for a /query request""" + + html_format_score: Optional[float] = None diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py index 26db1d83..318636b9 100644 --- a/src/codex/types/projects/entry_query_response.py +++ b/src/codex/types/projects/entry_query_response.py @@ -8,13 +8,31 @@ "EntryQueryResponse", "Entry", "EntryManagedMetadata", + "EntryManagedMetadataContentStructureScores", "EntryManagedMetadataContextSufficiency", + "EntryManagedMetadataHTMLFormatScores", "EntryManagedMetadataQueryEaseCustomized", "EntryManagedMetadataResponseHelpfulness", "EntryManagedMetadataTrustworthiness", ] +class EntryManagedMetadataContentStructureScores(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class EntryManagedMetadataContextSufficiency(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -31,6 +49,22 @@ class EntryManagedMetadataContextSufficiency(BaseModel): scores: Optional[List[float]] = None +class EntryManagedMetadataHTMLFormatScores(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class EntryManagedMetadataQueryEaseCustomized(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -92,6 +126,9 @@ class EntryManagedMetadata(BaseModel): latest_location: Optional[str] = None """The most recent location string.""" + content_structure_scores: Optional[EntryManagedMetadataContentStructureScores] = None + """Holds a list of scores and computes aggregate statistics.""" + context_sufficiency: Optional[EntryManagedMetadataContextSufficiency] = None """Holds a list of scores and computes aggregate statistics.""" @@ -99,6 +136,9 @@ class EntryManagedMetadata(BaseModel): entry_points: Optional[List[str]] = None + html_format_scores: Optional[EntryManagedMetadataHTMLFormatScores] = None + """Holds a list of scores and computes aggregate statistics.""" + llm_responses: Optional[List[str]] = None locations: Optional[List[str]] = None diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py index 53706c6c..496236f8 100644 --- a/tests/api_resources/projects/test_clusters.py +++ b/tests/api_resources/projects/test_clusters.py @@ -32,6 +32,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: cluster = client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", eval_issue_types=["hallucination"], + instruction_adherence_failure="html_format", limit=1, offset=0, order="asc", @@ -146,6 +147,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No cluster = await async_client.projects.clusters.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", eval_issue_types=["hallucination"], + instruction_adherence_failure="html_format", limit=1, offset=0, order="asc", From f08afc157ddc4481ab2ec37b6f03fb2ebd7b5b33 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:16:35 +0000 Subject: [PATCH 126/320] feat(api): api update --- .release-please-manifest.json | 2 +- .stats.yml | 2 +- api.md | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- src/codex/resources/projects/projects.py | 17 ++++++++-- src/codex/types/__init__.py | 1 + .../types/project_increment_queries_params.py | 11 ++++++ tests/api_resources/test_projects.py | 34 ++++++++++++++----- 9 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 src/codex/types/project_increment_queries_params.py diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e2f2c074..3cf71e62 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.17" + ".": "0.1.0-alpha.18" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 7a2f56a6..4a4a129e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 43 -openapi_spec_hash: b7beefbd38b4fcdd191cdb81a18a023b +openapi_spec_hash: 51dd9bdb04307116617d3eefe3237755 config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/api.md b/api.md index f240632f..3bd2cf4f 100644 --- a/api.md +++ b/api.md @@ -153,7 +153,7 @@ Methods: - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None - client.projects.export(project_id) -> object -- client.projects.increment_queries(project_id) -> object +- client.projects.increment_queries(project_id, \*\*params) -> object - client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse ## AccessKeys diff --git a/pyproject.toml b/pyproject.toml index 24f75152..ff2faf37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.17" +version = "0.1.0-alpha.18" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 264d49e0..29c60372 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.17" # x-release-please-version +__version__ = "0.1.0-alpha.18" # x-release-please-version diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index bd50a684..7c676b6d 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -11,6 +11,7 @@ project_list_params, project_create_params, project_update_params, + project_increment_queries_params, project_retrieve_analytics_params, ) from .entries import ( @@ -331,6 +332,7 @@ def increment_queries( self, project_id: str, *, + count: int | NotGiven = NOT_GIVEN, # 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, @@ -355,7 +357,11 @@ def increment_queries( return self._post( f"/api/projects/{project_id}/increment_queries", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams), ), cast_to=object, ) @@ -685,6 +691,7 @@ async def increment_queries( self, project_id: str, *, + count: int | NotGiven = NOT_GIVEN, # 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, @@ -709,7 +716,13 @@ async def increment_queries( return await self._post( f"/api/projects/{project_id}/increment_queries", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams + ), ), cast_to=object, ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 53d1ab6b..7f18b9c1 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -15,6 +15,7 @@ from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams +from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse diff --git a/src/codex/types/project_increment_queries_params.py b/src/codex/types/project_increment_queries_params.py new file mode 100644 index 00000000..f6043a76 --- /dev/null +++ b/src/codex/types/project_increment_queries_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ProjectIncrementQueriesParams"] + + +class ProjectIncrementQueriesParams(TypedDict, total=False): + count: int diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 772a7b29..40f40eb7 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -322,7 +322,16 @@ def test_path_params_export(self, client: Codex) -> None: @parametrize def test_method_increment_queries(self, client: Codex) -> None: project = client.projects.increment_queries( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_increment_queries_with_all_params(self, client: Codex) -> None: + project = client.projects.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + count=0, ) assert_matches_type(object, project, path=["response"]) @@ -330,7 +339,7 @@ def test_method_increment_queries(self, client: Codex) -> None: @parametrize def test_raw_response_increment_queries(self, client: Codex) -> None: response = client.projects.with_raw_response.increment_queries( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -342,7 +351,7 @@ def test_raw_response_increment_queries(self, client: Codex) -> None: @parametrize def test_streaming_response_increment_queries(self, client: Codex) -> None: with client.projects.with_streaming_response.increment_queries( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -357,7 +366,7 @@ def test_streaming_response_increment_queries(self, client: Codex) -> None: def test_path_params_increment_queries(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): client.projects.with_raw_response.increment_queries( - "", + project_id="", ) @pytest.mark.skip() @@ -716,7 +725,16 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_increment_queries(self, async_client: AsyncCodex) -> None: project = await async_client.projects.increment_queries( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_increment_queries_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + count=0, ) assert_matches_type(object, project, path=["response"]) @@ -724,7 +742,7 @@ async def test_method_increment_queries(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.increment_queries( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -736,7 +754,7 @@ async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> @parametrize async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.increment_queries( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -751,7 +769,7 @@ async def test_streaming_response_increment_queries(self, async_client: AsyncCod async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): await async_client.projects.with_raw_response.increment_queries( - "", + project_id="", ) @pytest.mark.skip() From fdb4753fc81b3fb06e2f16034f2734a4e2f3af9c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 19:16:21 +0000 Subject: [PATCH 127/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/users/user_schema.py | 2 ++ src/codex/types/users/user_schema_public.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4a4a129e..4fd111cc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 43 -openapi_spec_hash: 51dd9bdb04307116617d3eefe3237755 +openapi_spec_hash: 6d2d01f4951c677a47cffe973084413e config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/src/codex/types/users/user_schema.py b/src/codex/types/users/user_schema.py index b1665f21..d22c9563 100644 --- a/src/codex/types/users/user_schema.py +++ b/src/codex/types/users/user_schema.py @@ -23,6 +23,8 @@ class UserSchema(BaseModel): email: str + email_verified: bool + updated_at: datetime user_provided_company_name: Optional[str] = None diff --git a/src/codex/types/users/user_schema_public.py b/src/codex/types/users/user_schema_public.py index 181113b0..d5e1d9bf 100644 --- a/src/codex/types/users/user_schema_public.py +++ b/src/codex/types/users/user_schema_public.py @@ -14,7 +14,7 @@ class UserSchemaPublic(BaseModel): email: str - email_verified: Optional[bool] = None + email_verified: bool first_name: Optional[str] = None From f402cad3cff3175741531cd63498e38c8d54d601 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:17:04 +0000 Subject: [PATCH 128/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/entries.py | 12 ++++++ .../types/projects/entry_query_params.py | 39 ++++++++++++++++++- tests/api_resources/projects/test_entries.py | 12 ++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4fd111cc..a1b247c4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 43 -openapi_spec_hash: 6d2d01f4951c677a47cffe973084413e +openapi_spec_hash: 3873591605b529e6ae298fc7f04d4ba1 config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index a9e690b9..346dd353 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -319,6 +319,7 @@ def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, + query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -334,6 +335,10 @@ def query( Query Entries Route Args: + client_metadata: Deprecated: Use query_metadata instead + + query_metadata: Optional logging data that can be provided by the client. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -361,6 +366,7 @@ def query( { "question": question, "client_metadata": client_metadata, + "query_metadata": query_metadata, }, entry_query_params.EntryQueryParams, ), @@ -708,6 +714,7 @@ async def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, + query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -723,6 +730,10 @@ async def query( Query Entries Route Args: + client_metadata: Deprecated: Use query_metadata instead + + query_metadata: Optional logging data that can be provided by the client. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -750,6 +761,7 @@ async def query( { "question": question, "client_metadata": client_metadata, + "query_metadata": query_metadata, }, entry_query_params.EntryQueryParams, ), diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index d58b7bfa..1edabbed 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import Optional +from typing import Dict, List, Union, Iterable, Optional from typing_extensions import Required, Annotated, TypedDict from ..._utils import PropertyInfo -__all__ = ["EntryQueryParams"] +__all__ = ["EntryQueryParams", "QueryMetadata", "QueryMetadataContextUnionMember3"] class EntryQueryParams(TypedDict, total=False): @@ -16,6 +16,10 @@ class EntryQueryParams(TypedDict, total=False): use_llm_matching: bool client_metadata: Optional[object] + """Deprecated: Use query_metadata instead""" + + query_metadata: Optional[QueryMetadata] + """Optional logging data that can be provided by the client.""" x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] @@ -24,3 +28,34 @@ class EntryQueryParams(TypedDict, total=False): x_source: Annotated[str, PropertyInfo(alias="x-source")] x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] + + +class QueryMetadataContextUnionMember3(TypedDict, total=False): + content: Required[str] + """The actual content/text of the document.""" + + id: Optional[str] + """Unique identifier for the document. Useful for tracking documents""" + + source: Optional[str] + """Source or origin of the document. Useful for citations.""" + + tags: Optional[List[str]] + """Tags or categories for the document. Useful for filtering""" + + title: Optional[str] + """Title or heading of the document. Useful for display and context.""" + + +class QueryMetadata(TypedDict, total=False): + context: Union[str, List[str], Iterable[object], Iterable[QueryMetadataContextUnionMember3], None] + """RAG context used for the query""" + + custom_metadata: Optional[object] + """Arbitrary metadata supplied by the user/system""" + + eval_scores: Optional[Dict[str, float]] + """Evaluation scores for the original response""" + + evaluated_response: Optional[str] + """The response being evaluated from the RAG system(before any remediation)""" diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 31a5e408..73a45ad4 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -396,6 +396,12 @@ def test_method_query_with_all_params(self, client: Codex) -> None: question="question", use_llm_matching=True, client_metadata={}, + query_metadata={ + "context": "string", + "custom_metadata": {}, + "eval_scores": {"foo": 0}, + "evaluated_response": "evaluated_response", + }, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -871,6 +877,12 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N question="question", use_llm_matching=True, client_metadata={}, + query_metadata={ + "context": "string", + "custom_metadata": {}, + "eval_scores": {"foo": 0}, + "evaluated_response": "evaluated_response", + }, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", From 9a1695b09bea5438bf73eb151927d845e5c66d5d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 18:16:50 +0000 Subject: [PATCH 129/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/tlm.py | 272 ++++++++++++++------------- src/codex/types/tlm_prompt_params.py | 67 +++---- src/codex/types/tlm_score_params.py | 67 +++---- 4 files changed, 215 insertions(+), 193 deletions(-) diff --git a/.stats.yml b/.stats.yml index a1b247c4..bcd9e36c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 43 -openapi_spec_hash: 3873591605b529e6ae298fc7f04d4ba1 +openapi_spec_hash: f0d588a39e2040ff516a5cff26c4ab58 config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 78f97e2e..5e4cd7e1 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -97,59 +97,63 @@ def prompt( You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", - "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o1", - "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", - "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", - "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", - "gpt-4o-mini". + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. If you experience token/rate limit errors while using TLM, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. - TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). - This parameter must be between 1 and 20. - When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. - This consistency helps quantify the epistemic uncertainty associated with + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it - generated and self-evaluate this response. - Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. - Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches answers that are obviously incorrect/bad. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures - similarity between sampled responses considered by the model in the consistency assessment. - Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), - "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). - Set this to "string" to improve latency/costs. + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) - when considering alternative possible responses and double-checking responses. - Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -234,59 +238,63 @@ def score( You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", - "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o1", - "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", - "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", - "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", - "gpt-4o-mini". + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. If you experience token/rate limit errors while using TLM, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. - TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). - This parameter must be between 1 and 20. - When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. - This consistency helps quantify the epistemic uncertainty associated with + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it - generated and self-evaluate this response. - Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. - Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches answers that are obviously incorrect/bad. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures - similarity between sampled responses considered by the model in the consistency assessment. - Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), - "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). - Set this to "string" to improve latency/costs. + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) - when considering alternative possible responses and double-checking responses. - Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -387,59 +395,63 @@ async def prompt( You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", - "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o1", - "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", - "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", - "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", - "gpt-4o-mini". + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. If you experience token/rate limit errors while using TLM, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. - TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). - This parameter must be between 1 and 20. - When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. - This consistency helps quantify the epistemic uncertainty associated with + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it - generated and self-evaluate this response. - Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. - Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches answers that are obviously incorrect/bad. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures - similarity between sampled responses considered by the model in the consistency assessment. - Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), - "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). - Set this to "string" to improve latency/costs. + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) - when considering alternative possible responses and double-checking responses. - Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -524,59 +536,63 @@ async def score( You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", - "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o1", - "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", - "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", - "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", - "gpt-4o-mini". + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. If you experience token/rate limit errors while using TLM, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. - TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). - This parameter must be between 1 and 20. - When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. - This consistency helps quantify the epistemic uncertainty associated with + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it - generated and self-evaluate this response. - Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. - Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches answers that are obviously incorrect/bad. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures - similarity between sampled responses considered by the model in the consistency assessment. - Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), - "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). - Set this to "string" to improve latency/costs. + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) - when considering alternative possible responses and double-checking responses. - Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 860f1a77..94536055 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -48,61 +48,64 @@ class TlmPromptParams(TypedDict, total=False): You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", - "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o1", - "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", - "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", - "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", - "gpt-4o-mini". + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. If you experience token/rate limit errors while using TLM, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. - TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). - This parameter must be between 1 and 20. - When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. - This consistency helps quantify the epistemic uncertainty associated with + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it - generated and self-evaluate this response. - Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. - Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches answers that are obviously incorrect/bad. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures - similarity between sampled responses considered by the model in the consistency assessment. - Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), - "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). - Set this to "string" to improve latency/costs. + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) - when considering alternative possible responses and double-checking responses. - Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. """ quality_preset: Literal["best", "high", "medium", "low", "base"] + """The quality preset to use for the TLM or Trustworthy RAG API.""" task: Optional[str] diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index 213da422..a0d90175 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -50,61 +50,64 @@ class TlmScoreParams(TypedDict, total=False): You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "o1-preview", - "gpt-3.5-turbo-16k", "gpt-4", "gpt-4.5-preview", "claude-3.7-sonnet", + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o1", - "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-haiku", "nova-micro", "nova-lite", - "nova-pro". - Recommended models for accuracy: "gpt-4o", "o3-mini", "o1", - "claude-3.7-sonnet". - Recommended models for low latency/costs: "nova-micro", - "gpt-4o-mini". + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher costs/runtimes. + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. If you experience token/rate limit errors while using TLM, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated by TLM. - TLM scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - Higher values here can produce better (more accurate) responses from the TLM, but at higher costs/runtimes (and internally consumes more tokens). - This parameter must be between 1 and 20. - When it is 1, TLM simply returns a standard LLM response and does not attempt to auto-improve it. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - num_consistency_samples (int, default = 8): the amount of internal sampling to evaluate LLM response consistency. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher costs/runtimes. - This consistency helps quantify the epistemic uncertainty associated with + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM internally measures consistency via the degree of contradiction between sampled responses that the model considers equally plausible. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to self-reflect upon the response it - generated and self-evaluate this response. - Setting this False disables self-reflection and may worsen trustworthiness scores, but will reduce costs/runtimes. - Self-reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches answers that are obviously incorrect/bad. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large"}, default = "semantic"): how the trustworthiness scoring algorithm measures - similarity between sampled responses considered by the model in the consistency assessment. - Supported similarity measures include "semantic" (based on natural language inference), "string" (based on character/word overlap), - "embedding" (based on embedding similarity), and "embedding_large" (based on embedding similarity with a larger embedding model). - Set this to "string" to improve latency/costs. + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much the LLM can reason (number of thinking tokens) - when considering alternative possible responses and double-checking responses. - Higher efforts here may produce better TLM trustworthiness scores and LLM responses. Reduce this value to improve latency/costs. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria. + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. """ quality_preset: Literal["best", "high", "medium", "low", "base"] + """The quality preset to use for the TLM or Trustworthy RAG API.""" task: Optional[str] From a5e9f6ecc3ba04b17238fa822be1f14dc96c5bbf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 17:16:56 +0000 Subject: [PATCH 130/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index bcd9e36c..d087a84d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 43 -openapi_spec_hash: f0d588a39e2040ff516a5cff26c4ab58 +openapi_spec_hash: 97719fe7ae4c641a5a020dd21f2978dd config_hash: 5e459b33c53ffa6e554087a779bdb790 From a5bbebe9f374a323ea3cb6d48b8a8e320de43a5c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 17:36:40 +0000 Subject: [PATCH 131/320] feat(api): add validate endpoint --- .stats.yml | 4 +- api.md | 2 + src/codex/resources/projects/projects.py | 378 ++++++++++++++++++- src/codex/types/__init__.py | 2 + src/codex/types/project_validate_params.py | 169 +++++++++ src/codex/types/project_validate_response.py | 36 ++ tests/api_resources/test_projects.py | 195 ++++++++++ 7 files changed, 782 insertions(+), 4 deletions(-) create mode 100644 src/codex/types/project_validate_params.py create mode 100644 src/codex/types/project_validate_response.py diff --git a/.stats.yml b/.stats.yml index d087a84d..f01e1b9f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 43 +configured_endpoints: 44 openapi_spec_hash: 97719fe7ae4c641a5a020dd21f2978dd -config_hash: 5e459b33c53ffa6e554087a779bdb790 +config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/api.md b/api.md index 3bd2cf4f..f3a2ea14 100644 --- a/api.md +++ b/api.md @@ -142,6 +142,7 @@ from codex.types import ( ProjectExportResponse, ProjectIncrementQueriesResponse, ProjectRetrieveAnalyticsResponse, + ProjectValidateResponse, ) ``` @@ -155,6 +156,7 @@ Methods: - client.projects.export(project_id) -> object - client.projects.increment_queries(project_id, \*\*params) -> object - client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse +- client.projects.validate(project_id, \*\*params) -> ProjectValidateResponse ## AccessKeys diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 7c676b6d..b8fbaf7e 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Dict, List, Optional from typing_extensions import Literal import httpx @@ -11,6 +11,7 @@ project_list_params, project_create_params, project_update_params, + project_validate_params, project_increment_queries_params, project_retrieve_analytics_params, ) @@ -23,7 +24,7 @@ AsyncEntriesResourceWithStreamingResponse, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from .clusters import ( ClustersResource, AsyncClustersResource, @@ -52,6 +53,7 @@ from ...types.project_list_response import ProjectListResponse from ...types.project_return_schema import ProjectReturnSchema from ...types.project_retrieve_response import ProjectRetrieveResponse +from ...types.project_validate_response import ProjectValidateResponse from ...types.project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -415,6 +417,186 @@ def retrieve_analytics( cast_to=ProjectRetrieveAnalyticsResponse, ) + def validate( + self, + project_id: str, + *, + context: str, + prompt: str, + query: str, + response: str, + use_llm_matching: bool | NotGiven = NOT_GIVEN, + bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, + eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, + options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, + quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + task: Optional[str] | NotGiven = NOT_GIVEN, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectValidateResponse: + """ + Evaluate whether a response, given the provided query and context, is + potentially bad. If the response is flagged as bad, a lookup is performed to + find an alternate expert answer. If there is no expert answer available, this + query will be recorded in the project for SMEs to answer. + + Args: + custom_metadata: Arbitrary metadata supplied by the user/system + + eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM + will be used to generate scores. + + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } + return self._post( + f"/api/projects/{project_id}/validate", + body=maybe_transform( + { + "context": context, + "prompt": prompt, + "query": query, + "response": response, + "bad_response_thresholds": bad_response_thresholds, + "constrain_outputs": constrain_outputs, + "custom_metadata": custom_metadata, + "eval_scores": eval_scores, + "options": options, + "quality_preset": quality_preset, + "task": task, + }, + project_validate_params.ProjectValidateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"use_llm_matching": use_llm_matching}, project_validate_params.ProjectValidateParams + ), + ), + cast_to=ProjectValidateResponse, + ) + class AsyncProjectsResource(AsyncAPIResource): @cached_property @@ -776,6 +958,186 @@ async def retrieve_analytics( cast_to=ProjectRetrieveAnalyticsResponse, ) + async def validate( + self, + project_id: str, + *, + context: str, + prompt: str, + query: str, + response: str, + use_llm_matching: bool | NotGiven = NOT_GIVEN, + bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, + eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, + options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, + quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + task: Optional[str] | NotGiven = NOT_GIVEN, + x_client_library_version: str | NotGiven = NOT_GIVEN, + x_integration_type: str | NotGiven = NOT_GIVEN, + x_source: str | NotGiven = NOT_GIVEN, + x_stainless_package_version: str | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectValidateResponse: + """ + Evaluate whether a response, given the provided query and context, is + potentially bad. If the response is flagged as bad, a lookup is performed to + find an alternate expert answer. If there is no expert answer available, this + query will be recorded in the project for SMEs to answer. + + Args: + custom_metadata: Arbitrary metadata supplied by the user/system + + eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM + will be used to generate scores. + + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + extra_headers = { + **strip_not_given( + { + "x-client-library-version": x_client_library_version, + "x-integration-type": x_integration_type, + "x-source": x_source, + "x-stainless-package-version": x_stainless_package_version, + } + ), + **(extra_headers or {}), + } + return await self._post( + f"/api/projects/{project_id}/validate", + body=await async_maybe_transform( + { + "context": context, + "prompt": prompt, + "query": query, + "response": response, + "bad_response_thresholds": bad_response_thresholds, + "constrain_outputs": constrain_outputs, + "custom_metadata": custom_metadata, + "eval_scores": eval_scores, + "options": options, + "quality_preset": quality_preset, + "task": task, + }, + project_validate_params.ProjectValidateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"use_llm_matching": use_llm_matching}, project_validate_params.ProjectValidateParams + ), + ), + cast_to=ProjectValidateResponse, + ) + class ProjectsResourceWithRawResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -805,6 +1167,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.retrieve_analytics = to_raw_response_wrapper( projects.retrieve_analytics, ) + self.validate = to_raw_response_wrapper( + projects.validate, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithRawResponse: @@ -847,6 +1212,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.retrieve_analytics = async_to_raw_response_wrapper( projects.retrieve_analytics, ) + self.validate = async_to_raw_response_wrapper( + projects.validate, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: @@ -889,6 +1257,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.retrieve_analytics = to_streamed_response_wrapper( projects.retrieve_analytics, ) + self.validate = to_streamed_response_wrapper( + projects.validate, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithStreamingResponse: @@ -931,6 +1302,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.retrieve_analytics = async_to_streamed_response_wrapper( projects.retrieve_analytics, ) + self.validate = async_to_streamed_response_wrapper( + projects.validate, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 7f18b9c1..8e0cc4a4 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -12,7 +12,9 @@ from .project_list_response import ProjectListResponse as ProjectListResponse from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema from .project_update_params import ProjectUpdateParams as ProjectUpdateParams +from .project_validate_params import ProjectValidateParams as ProjectValidateParams from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse +from .project_validate_response import ProjectValidateResponse as ProjectValidateResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py new file mode 100644 index 00000000..f6214cbe --- /dev/null +++ b/src/codex/types/project_validate_params.py @@ -0,0 +1,169 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Iterable, Optional +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ProjectValidateParams", "BadResponseThresholds", "Options"] + + +class ProjectValidateParams(TypedDict, total=False): + context: Required[str] + + prompt: Required[str] + + query: Required[str] + + response: Required[str] + + use_llm_matching: bool + + bad_response_thresholds: BadResponseThresholds + + constrain_outputs: Optional[List[str]] + + custom_metadata: Optional[object] + """Arbitrary metadata supplied by the user/system""" + + eval_scores: Optional[Dict[str, float]] + """Evaluation scores to use for flagging a response as bad. + + If not provided, TLM will be used to generate scores. + """ + + options: Optional[Options] + """ + Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a cheaper self-reflection will be used to compute the + trustworthiness score. + + By default, the TLM uses the "medium" quality preset. The default base LLM + `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. + You can set custom values for these arguments regardless of the quality preset + specified. + + Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", + "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = + "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, + faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", + "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models + for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", + "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: + "gpt-4.1-nano", "nova-micro". + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). + Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. + If you experience token/rate limit errors while using TLM, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. + When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. + Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include: "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + """ + + quality_preset: Literal["best", "high", "medium", "low", "base"] + """The quality preset to use for the TLM or Trustworthy RAG API.""" + + task: Optional[str] + + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] + + x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] + + x_source: Annotated[str, PropertyInfo(alias="x-source")] + + x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] + + +class BadResponseThresholds(TypedDict, total=False): + context_sufficiency: Optional[float] + + query_ease: Optional[float] + + response_helpfulness: Optional[float] + + trustworthiness: Optional[float] + + +class Options(TypedDict, total=False): + custom_eval_criteria: Iterable[object] + + log: List[str] + + max_tokens: int + + model: str + + num_candidate_responses: int + + num_consistency_samples: int + + reasoning_effort: str + + similarity_measure: str + + use_self_reflection: bool diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py new file mode 100644 index 00000000..e2104360 --- /dev/null +++ b/src/codex/types/project_validate_response.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from .._models import BaseModel + +__all__ = ["ProjectValidateResponse", "EvalScores"] + + +class EvalScores(BaseModel): + is_bad: bool + + score: Optional[float] = None + + log: Optional[object] = None + + +class ProjectValidateResponse(BaseModel): + eval_scores: Dict[str, EvalScores] + """ + Evaluation scores for the original response along with a boolean flag, `is_bad`, + indicating whether the score is below the threshold. + """ + + expert_answer: Optional[str] = None + """ + Alternate SME-provided answer from Codex if the response was flagged as bad and + an answer was found in the Codex Project, or None otherwise. + """ + + is_bad_response: bool + """True if the response is flagged as potentially bad, False otherwise. + + When True, a lookup is performed, which logs this query in the project for SMEs + to answer, if it does not already exist. + """ diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 40f40eb7..f7ca6e01 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -12,6 +12,7 @@ ProjectListResponse, ProjectReturnSchema, ProjectRetrieveResponse, + ProjectValidateResponse, ProjectRetrieveAnalyticsResponse, ) from tests.utils import assert_matches_type @@ -421,6 +422,103 @@ def test_path_params_retrieve_analytics(self, client: Codex) -> None: project_id="", ) + @pytest.mark.skip() + @parametrize + def test_method_validate(self, client: Codex) -> None: + project = client.projects.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + ) + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_validate_with_all_params(self, client: Codex) -> None: + project = client.projects.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + use_llm_matching=True, + bad_response_thresholds={ + "context_sufficiency": 0, + "query_ease": 0, + "response_helpfulness": 0, + "trustworthiness": 0, + }, + constrain_outputs=["string"], + custom_metadata={}, + eval_scores={"foo": 0}, + options={ + "custom_eval_criteria": [{}], + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + task="task", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_validate(self, client: Codex) -> None: + response = client.projects.with_raw_response.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_validate(self, client: Codex) -> None: + with client.projects.with_streaming_response.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_validate(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.validate( + project_id="", + context="context", + prompt="prompt", + query="query", + response="response", + ) + class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -823,3 +921,100 @@ async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) -> await async_client.projects.with_raw_response.retrieve_analytics( project_id="", ) + + @pytest.mark.skip() + @parametrize + async def test_method_validate(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + ) + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + use_llm_matching=True, + bad_response_thresholds={ + "context_sufficiency": 0, + "query_ease": 0, + "response_helpfulness": 0, + "trustworthiness": 0, + }, + constrain_outputs=["string"], + custom_metadata={}, + eval_scores={"foo": 0}, + options={ + "custom_eval_criteria": [{}], + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + task="task", + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_validate(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_validate(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.validate( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + prompt="prompt", + query="query", + response="response", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectValidateResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_validate(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.validate( + project_id="", + context="context", + prompt="prompt", + query="query", + response="response", + ) From 59053e68d8fb359f2c65fcd1f460bfebd781ee35 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 18:36:24 +0000 Subject: [PATCH 132/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3cf71e62..b386befd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.18" + ".": "0.1.0-alpha.19" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ff2faf37..b288d3ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.18" +version = "0.1.0-alpha.19" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 29c60372..87d42e64 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.18" # x-release-please-version +__version__ = "0.1.0-alpha.19" # x-release-please-version From 2dcaf678b95e7fb119409e5b28c138ce280664c3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 8 May 2025 20:16:50 +0000 Subject: [PATCH 133/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f01e1b9f..f3d66113 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 97719fe7ae4c641a5a020dd21f2978dd +openapi_spec_hash: 3b6849447c1956c43e53ce519056090e config_hash: 659f65b6ccf5612986f920f7f9abbcb5 From 9881bd21d15f23b73e02f16177d573656a10d4f3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 02:44:01 +0000 Subject: [PATCH 134/320] chore(internal): avoid errors for isinstance checks on proxies --- src/codex/_utils/_proxy.py | 5 ++++- tests/test_utils/test_proxy.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/codex/_utils/_proxy.py b/src/codex/_utils/_proxy.py index ffd883e9..0f239a33 100644 --- a/src/codex/_utils/_proxy.py +++ b/src/codex/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index 1277e93e..0a34f4d9 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) From 8b1b0da7a20c73ed1f205e31b862d7a301f98e17 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 10 May 2025 02:33:35 +0000 Subject: [PATCH 135/320] fix(package): support direct resource imports --- src/codex/__init__.py | 5 +++++ src/codex/_utils/_resources_proxy.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/codex/_utils/_resources_proxy.py diff --git a/src/codex/__init__.py b/src/codex/__init__.py index d6dffe2c..5c5f678c 100644 --- a/src/codex/__init__.py +++ b/src/codex/__init__.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import typing as _t + from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path @@ -80,6 +82,9 @@ "DefaultAsyncHttpxClient", ] +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/codex/_utils/_resources_proxy.py b/src/codex/_utils/_resources_proxy.py new file mode 100644 index 00000000..346e8681 --- /dev/null +++ b/src/codex/_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 `codex.resources` module. + + This is used so that we can lazily import `codex.resources` only when + needed *and* so that users can just import `codex` and reference `codex.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("codex.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() From 2c5294df9c841d0dc156f39699717e97d0962833 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 14:16:48 +0000 Subject: [PATCH 136/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f3d66113..a0ce2326 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 3b6849447c1956c43e53ce519056090e +openapi_spec_hash: 62dd5958a2e393554eebfff1f9ec4e9c config_hash: 659f65b6ccf5612986f920f7f9abbcb5 From 157909ede529c43a38c6d5b1831fe02626a55d50 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 19:16:54 +0000 Subject: [PATCH 137/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index a0ce2326..59d48ea4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 62dd5958a2e393554eebfff1f9ec4e9c +openapi_spec_hash: 8ffde9b129ffc5edd4c4f8c9d866d869 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 From 7652365c55705a5fb2300b039b91d02893b24233 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 19:16:46 +0000 Subject: [PATCH 138/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 22 +++++++++++++------- src/codex/types/project_validate_params.py | 22 +++++++------------- src/codex/types/project_validate_response.py | 4 ++-- tests/api_resources/test_projects.py | 14 ++----------- 5 files changed, 27 insertions(+), 37 deletions(-) diff --git a/.stats.yml b/.stats.yml index 59d48ea4..b18a9ded 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 8ffde9b129ffc5edd4c4f8c9d866d869 +openapi_spec_hash: 056bc3805c2373563a6585103edd5cb8 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index b8fbaf7e..6195d1a4 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -426,8 +426,8 @@ def validate( query: str, response: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, - bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, @@ -451,10 +451,13 @@ def validate( query will be recorded in the project for SMEs to answer. Args: + custom_eval_thresholds: Optional custom thresholds for specific evals. Keys should match with the keys + in the `eval_scores` dictionary. + custom_metadata: Arbitrary metadata supplied by the user/system - eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM - will be used to generate scores. + eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will + be used to generate scores. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -575,8 +578,8 @@ def validate( "prompt": prompt, "query": query, "response": response, - "bad_response_thresholds": bad_response_thresholds, "constrain_outputs": constrain_outputs, + "custom_eval_thresholds": custom_eval_thresholds, "custom_metadata": custom_metadata, "eval_scores": eval_scores, "options": options, @@ -967,8 +970,8 @@ async def validate( query: str, response: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, - bad_response_thresholds: project_validate_params.BadResponseThresholds | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, @@ -992,10 +995,13 @@ async def validate( query will be recorded in the project for SMEs to answer. Args: + custom_eval_thresholds: Optional custom thresholds for specific evals. Keys should match with the keys + in the `eval_scores` dictionary. + custom_metadata: Arbitrary metadata supplied by the user/system - eval_scores: Evaluation scores to use for flagging a response as bad. If not provided, TLM - will be used to generate scores. + eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will + be used to generate scores. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -1116,8 +1122,8 @@ async def validate( "prompt": prompt, "query": query, "response": response, - "bad_response_thresholds": bad_response_thresholds, "constrain_outputs": constrain_outputs, + "custom_eval_thresholds": custom_eval_thresholds, "custom_metadata": custom_metadata, "eval_scores": eval_scores, "options": options, diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index f6214cbe..a855aa6f 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -7,7 +7,7 @@ from .._utils import PropertyInfo -__all__ = ["ProjectValidateParams", "BadResponseThresholds", "Options"] +__all__ = ["ProjectValidateParams", "Options"] class ProjectValidateParams(TypedDict, total=False): @@ -21,15 +21,19 @@ class ProjectValidateParams(TypedDict, total=False): use_llm_matching: bool - bad_response_thresholds: BadResponseThresholds - constrain_outputs: Optional[List[str]] + custom_eval_thresholds: Optional[Dict[str, float]] + """Optional custom thresholds for specific evals. + + Keys should match with the keys in the `eval_scores` dictionary. + """ + custom_metadata: Optional[object] """Arbitrary metadata supplied by the user/system""" eval_scores: Optional[Dict[str, float]] - """Evaluation scores to use for flagging a response as bad. + """Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. """ @@ -139,16 +143,6 @@ class ProjectValidateParams(TypedDict, total=False): x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] -class BadResponseThresholds(TypedDict, total=False): - context_sufficiency: Optional[float] - - query_ease: Optional[float] - - response_helpfulness: Optional[float] - - trustworthiness: Optional[float] - - class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index e2104360..a88874da 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -8,7 +8,7 @@ class EvalScores(BaseModel): - is_bad: bool + failed: bool score: Optional[float] = None @@ -18,7 +18,7 @@ class EvalScores(BaseModel): class ProjectValidateResponse(BaseModel): eval_scores: Dict[str, EvalScores] """ - Evaluation scores for the original response along with a boolean flag, `is_bad`, + Evaluation scores for the original response along with a boolean flag, `failed`, indicating whether the score is below the threshold. """ diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index f7ca6e01..19e41a0a 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -444,13 +444,8 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: query="query", response="response", use_llm_matching=True, - bad_response_thresholds={ - "context_sufficiency": 0, - "query_ease": 0, - "response_helpfulness": 0, - "trustworthiness": 0, - }, constrain_outputs=["string"], + custom_eval_thresholds={"foo": 0}, custom_metadata={}, eval_scores={"foo": 0}, options={ @@ -944,13 +939,8 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - query="query", response="response", use_llm_matching=True, - bad_response_thresholds={ - "context_sufficiency": 0, - "query_ease": 0, - "response_helpfulness": 0, - "trustworthiness": 0, - }, constrain_outputs=["string"], + custom_eval_thresholds={"foo": 0}, custom_metadata={}, eval_scores={"foo": 0}, options={ From 272030b37f2ef1fa76a6cebd70278f7f74078271 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 21:16:57 +0000 Subject: [PATCH 139/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/clusters.py | 8 ++++++-- src/codex/types/projects/cluster_list_params.py | 2 +- src/codex/types/projects/entry_notify_sme_params.py | 11 ++++++++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index b18a9ded..12a0365a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 056bc3805c2373563a6585103edd5cb8 +openapi_spec_hash: 9d81a4b0eca6d3629ba9d5432a65655c config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index fea7e28e..97124642 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -50,7 +50,9 @@ def list( self, project_id: str, *, - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + eval_issue_types: List[ + Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"] + ] | NotGiven = NOT_GIVEN, instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, @@ -177,7 +179,9 @@ def list( self, project_id: str, *, - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + eval_issue_types: List[ + Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"] + ] | NotGiven = NOT_GIVEN, instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py index cff2b85e..20284d84 100644 --- a/src/codex/types/projects/cluster_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -9,7 +9,7 @@ class ClusterListParams(TypedDict, total=False): - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] diff --git a/src/codex/types/projects/entry_notify_sme_params.py b/src/codex/types/projects/entry_notify_sme_params.py index 409f8bc5..3d06d1a6 100644 --- a/src/codex/types/projects/entry_notify_sme_params.py +++ b/src/codex/types/projects/entry_notify_sme_params.py @@ -18,4 +18,13 @@ class EntryNotifySmeParams(TypedDict, total=False): class ViewContext(TypedDict, total=False): page: Required[int] - filter: Literal["unanswered", "answered", "all", "hallucination", "search_failure", "unhelpful", "difficult_query"] + filter: Literal[ + "unanswered", + "answered", + "all", + "hallucination", + "search_failure", + "unhelpful", + "difficult_query", + "unsupported", + ] From 0ff32ac5f5eb5d4b2dbab2b32e3cd3735541da15 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 03:15:04 +0000 Subject: [PATCH 140/320] chore(ci): upload sdks to package manager --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ scripts/utils/upload-artifact.sh | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 scripts/utils/upload-artifact.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc62f784..c02247a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,3 +29,27 @@ jobs: - name: Run lints run: ./scripts/lint + + upload: + if: github.repository == 'stainless-sdks/codex-python' + timeout-minutes: 10 + name: upload + permissions: + contents: read + id-token: write + runs-on: depot-ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Get GitHub OIDC Token + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..ebb04789 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -exuo pipefail + +RESPONSE=$(curl -X POST "$URL" \ + -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=$(tar -cz . | curl -v -X PUT \ + -H "Content-Type: application/gzip" \ + --data-binary @- "$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: npm install 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi From 9a6a371e15c587dc3d35acdaad81085531ae87d8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 17:10:51 +0000 Subject: [PATCH 141/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b386befd..fac14074 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.19" + ".": "0.1.0-alpha.20" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b288d3ef..8a5a62c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.19" +version = "0.1.0-alpha.20" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 87d42e64..44d6131d 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.19" # x-release-please-version +__version__ = "0.1.0-alpha.20" # x-release-please-version From 1d13a34d9efb160a66e69697284e060a04aee341 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 18:16:44 +0000 Subject: [PATCH 142/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 12a0365a..76c12f55 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 9d81a4b0eca6d3629ba9d5432a65655c +openapi_spec_hash: 19d3afd940d8ed57b76401ef026e5f47 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 From 2c1712f57e52fd1b02e5b9779bb8de25862386c7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 02:39:50 +0000 Subject: [PATCH 143/320] chore(ci): fix installation instructions --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index ebb04789..8f922b51 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ 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: npm install 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From b5ed20cef51870d4dd939dff518c312a7c66ac49 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 02:50:02 +0000 Subject: [PATCH 144/320] chore(internal): codegen related update --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 8f922b51..62d150a4 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ 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/codex-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install --pre 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From 15ab506c9579aad2489058f16c587fc0ab2c741c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 18:16:58 +0000 Subject: [PATCH 145/320] feat(api): api update --- .stats.yml | 2 +- .../types/projects/cluster_list_response.py | 20 +++++++++++++++++++ src/codex/types/projects/entry.py | 20 +++++++++++++++++++ .../types/projects/entry_query_response.py | 20 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 76c12f55..aac346a7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 19d3afd940d8ed57b76401ef026e5f47 +openapi_spec_hash: f25ca671adcc0b224451c721048d9220 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py index 2e8b5426..1fc8bd5e 100644 --- a/src/codex/types/projects/cluster_list_response.py +++ b/src/codex/types/projects/cluster_list_response.py @@ -13,6 +13,7 @@ "ManagedMetadataContextSufficiency", "ManagedMetadataHTMLFormatScores", "ManagedMetadataQueryEaseCustomized", + "ManagedMetadataResponseGroundedness", "ManagedMetadataResponseHelpfulness", "ManagedMetadataTrustworthiness", ] @@ -82,6 +83,22 @@ class ManagedMetadataQueryEaseCustomized(BaseModel): scores: Optional[List[float]] = None +class ManagedMetadataResponseGroundedness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class ManagedMetadataResponseHelpfulness(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -147,6 +164,9 @@ class ManagedMetadata(BaseModel): query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None """Holds a list of scores and computes aggregate statistics.""" + response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None + """Holds a list of scores and computes aggregate statistics.""" + response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None """Holds a list of scores and computes aggregate statistics.""" diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py index eb2a2217..3f7a86da 100644 --- a/src/codex/types/projects/entry.py +++ b/src/codex/types/projects/entry.py @@ -13,6 +13,7 @@ "ManagedMetadataContextSufficiency", "ManagedMetadataHTMLFormatScores", "ManagedMetadataQueryEaseCustomized", + "ManagedMetadataResponseGroundedness", "ManagedMetadataResponseHelpfulness", "ManagedMetadataTrustworthiness", ] @@ -82,6 +83,22 @@ class ManagedMetadataQueryEaseCustomized(BaseModel): scores: Optional[List[float]] = None +class ManagedMetadataResponseGroundedness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class ManagedMetadataResponseHelpfulness(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -147,6 +164,9 @@ class ManagedMetadata(BaseModel): query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None """Holds a list of scores and computes aggregate statistics.""" + response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None + """Holds a list of scores and computes aggregate statistics.""" + response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None """Holds a list of scores and computes aggregate statistics.""" diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py index 318636b9..cd5a4c97 100644 --- a/src/codex/types/projects/entry_query_response.py +++ b/src/codex/types/projects/entry_query_response.py @@ -12,6 +12,7 @@ "EntryManagedMetadataContextSufficiency", "EntryManagedMetadataHTMLFormatScores", "EntryManagedMetadataQueryEaseCustomized", + "EntryManagedMetadataResponseGroundedness", "EntryManagedMetadataResponseHelpfulness", "EntryManagedMetadataTrustworthiness", ] @@ -81,6 +82,22 @@ class EntryManagedMetadataQueryEaseCustomized(BaseModel): scores: Optional[List[float]] = None +class EntryManagedMetadataResponseGroundedness(BaseModel): + average: Optional[float] = None + """The average of all scores.""" + + latest: Optional[float] = None + """The most recent score.""" + + max: Optional[float] = None + """The maximum score.""" + + min: Optional[float] = None + """The minimum score.""" + + scores: Optional[List[float]] = None + + class EntryManagedMetadataResponseHelpfulness(BaseModel): average: Optional[float] = None """The average of all scores.""" @@ -146,6 +163,9 @@ class EntryManagedMetadata(BaseModel): query_ease_customized: Optional[EntryManagedMetadataQueryEaseCustomized] = None """Holds a list of scores and computes aggregate statistics.""" + response_groundedness: Optional[EntryManagedMetadataResponseGroundedness] = None + """Holds a list of scores and computes aggregate statistics.""" + response_helpfulness: Optional[EntryManagedMetadataResponseHelpfulness] = None """Holds a list of scores and computes aggregate statistics.""" From 6e144060b73fb03f94616582a81045ca92eb11ef Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 22:16:41 +0000 Subject: [PATCH 146/320] feat(api): api update --- .stats.yml | 2 +- tests/api_resources/test_projects.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index aac346a7..374e6728 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: f25ca671adcc0b224451c721048d9220 +openapi_spec_hash: 67d5aeebff72f48ee4730227ca0b47c2 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 19e41a0a..5c29fddf 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -204,7 +204,7 @@ def test_method_list(self, client: Codex) -> None: def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( include_entry_counts=True, - limit=0, + limit=1, offset=0, order="asc", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", @@ -699,7 +699,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( include_entry_counts=True, - limit=0, + limit=1, offset=0, order="asc", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", From be3641fbf88d6305df84a2fc4903c8ceface327f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 02:29:17 +0000 Subject: [PATCH 147/320] chore(docs): grammar improvements --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 9fc6ee28..07808285 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Codex please follow the respective company's security reporting guidelines. +or products provided by Codex, please follow the respective company's security reporting guidelines. ### Codex Terms and Policies -Please contact team@cleanlab.ai for any questions or concerns regarding security of our services. +Please contact team@cleanlab.ai for any questions or concerns regarding the security of our services. --- From 8f49e9fec86ad6b78f4aa9d91cefb04a8d4b11c8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 19:16:40 +0000 Subject: [PATCH 148/320] feat(api): api update --- .stats.yml | 2 +- README.md | 66 +++++ src/codex/types/project_create_params.py | 285 ++++++++++++++++++- src/codex/types/project_list_response.py | 285 ++++++++++++++++++- src/codex/types/project_retrieve_response.py | 284 +++++++++++++++++- src/codex/types/project_return_schema.py | 284 +++++++++++++++++- src/codex/types/project_update_params.py | 285 ++++++++++++++++++- tests/api_resources/test_projects.py | 264 +++++++++++++++++ 8 files changed, 1742 insertions(+), 13 deletions(-) diff --git a/.stats.yml b/.stats.yml index 374e6728..e80f0e13 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 67d5aeebff72f48ee4730227ca0b47c2 +openapi_spec_hash: 0f1841fad65926e7ddfb22dd7a642b46 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/README.md b/README.md index c2f168fe..4663d2fd 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,72 @@ client = Codex() project_return_schema = client.projects.create( config={ "clustering_use_llm_matching": True, + "eval_config": { + "custom_evals": { + "evals": { + "foo": { + "criteria": "criteria", + "eval_key": "eval_key", + "name": "name", + "context_identifier": "context_identifier", + "enabled": True, + "priority": 0, + "query_identifier": "query_identifier", + "response_identifier": "response_identifier", + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + } + } + }, + "default_evals": { + "context_sufficiency": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "query_ease": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_groundedness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_helpfulness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "trustworthiness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + }, + }, "llm_matching_model": "llm_matching_model", "llm_matching_quality_preset": "llm_matching_quality_preset", "lower_llm_match_distance_threshold": 0, diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index ecdd194d..75892e0b 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -2,10 +2,22 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Required, TypedDict +from typing import Dict, Optional +from typing_extensions import Literal, Required, TypedDict -__all__ = ["ProjectCreateParams", "Config"] +__all__ = [ + "ProjectCreateParams", + "Config", + "ConfigEvalConfig", + "ConfigEvalConfigCustomEvals", + "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigDefaultEvals", + "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsTrustworthiness", +] class ProjectCreateParams(TypedDict, total=False): @@ -18,9 +30,276 @@ class ProjectCreateParams(TypedDict, total=False): description: Optional[str] +class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): + criteria: Required[str] + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigCustomEvals(TypedDict, total=False): + evals: Dict[str, ConfigEvalConfigCustomEvalsEvals] + + +class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvals(TypedDict, total=False): + context_sufficiency: ConfigEvalConfigDefaultEvalsContextSufficiency + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + query_ease: ConfigEvalConfigDefaultEvalsQueryEase + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_groundedness: ConfigEvalConfigDefaultEvalsResponseGroundedness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_helpfulness: ConfigEvalConfigDefaultEvalsResponseHelpfulness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + trustworthiness: ConfigEvalConfigDefaultEvalsTrustworthiness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + +class ConfigEvalConfig(TypedDict, total=False): + custom_evals: ConfigEvalConfigCustomEvals + """Configuration for custom evaluation metrics.""" + + default_evals: ConfigEvalConfigDefaultEvals + """Configuration for default evaluation metrics.""" + + class Config(TypedDict, total=False): clustering_use_llm_matching: bool + eval_config: ConfigEvalConfig + """Configuration for project-specific evaluation metrics""" + llm_matching_model: str llm_matching_quality_preset: str diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 2b4fec42..59d3bf81 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -1,16 +1,297 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel -__all__ = ["ProjectListResponse", "Project", "ProjectConfig"] +__all__ = [ + "ProjectListResponse", + "Project", + "ProjectConfig", + "ProjectConfigEvalConfig", + "ProjectConfigEvalConfigCustomEvals", + "ProjectConfigEvalConfigCustomEvalsEvals", + "ProjectConfigEvalConfigDefaultEvals", + "ProjectConfigEvalConfigDefaultEvalsContextSufficiency", + "ProjectConfigEvalConfigDefaultEvalsQueryEase", + "ProjectConfigEvalConfigDefaultEvalsResponseGroundedness", + "ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ProjectConfigEvalConfigDefaultEvalsTrustworthiness", +] + + +class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): + criteria: str + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ProjectConfigEvalConfigCustomEvals(BaseModel): + evals: Optional[Dict[str, ProjectConfigEvalConfigCustomEvalsEvals]] = None + + +class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ProjectConfigEvalConfigDefaultEvals(BaseModel): + context_sufficiency: Optional[ProjectConfigEvalConfigDefaultEvalsContextSufficiency] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + query_ease: Optional[ProjectConfigEvalConfigDefaultEvalsQueryEase] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_groundedness: Optional[ProjectConfigEvalConfigDefaultEvalsResponseGroundedness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_helpfulness: Optional[ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + trustworthiness: Optional[ProjectConfigEvalConfigDefaultEvalsTrustworthiness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + +class ProjectConfigEvalConfig(BaseModel): + custom_evals: Optional[ProjectConfigEvalConfigCustomEvals] = None + """Configuration for custom evaluation metrics.""" + + default_evals: Optional[ProjectConfigEvalConfigDefaultEvals] = None + """Configuration for default evaluation metrics.""" class ProjectConfig(BaseModel): clustering_use_llm_matching: Optional[bool] = None + eval_config: Optional[ProjectConfigEvalConfig] = None + """Configuration for project-specific evaluation metrics""" + llm_matching_model: Optional[str] = None llm_matching_quality_preset: Optional[str] = None diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 62209d32..a631f0c2 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -1,16 +1,296 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Dict, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel -__all__ = ["ProjectRetrieveResponse", "Config"] +__all__ = [ + "ProjectRetrieveResponse", + "Config", + "ConfigEvalConfig", + "ConfigEvalConfigCustomEvals", + "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigDefaultEvals", + "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsTrustworthiness", +] + + +class ConfigEvalConfigCustomEvalsEvals(BaseModel): + criteria: str + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigCustomEvals(BaseModel): + evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None + + +class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvals(BaseModel): + context_sufficiency: Optional[ConfigEvalConfigDefaultEvalsContextSufficiency] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + query_ease: Optional[ConfigEvalConfigDefaultEvalsQueryEase] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_groundedness: Optional[ConfigEvalConfigDefaultEvalsResponseGroundedness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_helpfulness: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + trustworthiness: Optional[ConfigEvalConfigDefaultEvalsTrustworthiness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + +class ConfigEvalConfig(BaseModel): + custom_evals: Optional[ConfigEvalConfigCustomEvals] = None + """Configuration for custom evaluation metrics.""" + + default_evals: Optional[ConfigEvalConfigDefaultEvals] = None + """Configuration for default evaluation metrics.""" class Config(BaseModel): clustering_use_llm_matching: Optional[bool] = None + eval_config: Optional[ConfigEvalConfig] = None + """Configuration for project-specific evaluation metrics""" + llm_matching_model: Optional[str] = None llm_matching_quality_preset: Optional[str] = None diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 51a6c1ad..7da2e615 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -1,16 +1,296 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Dict, Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel -__all__ = ["ProjectReturnSchema", "Config"] +__all__ = [ + "ProjectReturnSchema", + "Config", + "ConfigEvalConfig", + "ConfigEvalConfigCustomEvals", + "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigDefaultEvals", + "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsTrustworthiness", +] + + +class ConfigEvalConfigCustomEvalsEvals(BaseModel): + criteria: str + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigCustomEvals(BaseModel): + evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None + + +class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvals(BaseModel): + context_sufficiency: Optional[ConfigEvalConfigDefaultEvalsContextSufficiency] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + query_ease: Optional[ConfigEvalConfigDefaultEvalsQueryEase] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_groundedness: Optional[ConfigEvalConfigDefaultEvalsResponseGroundedness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_helpfulness: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + trustworthiness: Optional[ConfigEvalConfigDefaultEvalsTrustworthiness] = None + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + +class ConfigEvalConfig(BaseModel): + custom_evals: Optional[ConfigEvalConfigCustomEvals] = None + """Configuration for custom evaluation metrics.""" + + default_evals: Optional[ConfigEvalConfigDefaultEvals] = None + """Configuration for default evaluation metrics.""" class Config(BaseModel): clustering_use_llm_matching: Optional[bool] = None + eval_config: Optional[ConfigEvalConfig] = None + """Configuration for project-specific evaluation metrics""" + llm_matching_model: Optional[str] = None llm_matching_quality_preset: Optional[str] = None diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 0a5aa540..d58dd591 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -2,10 +2,22 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Required, TypedDict +from typing import Dict, Optional +from typing_extensions import Literal, Required, TypedDict -__all__ = ["ProjectUpdateParams", "Config"] +__all__ = [ + "ProjectUpdateParams", + "Config", + "ConfigEvalConfig", + "ConfigEvalConfigCustomEvals", + "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigDefaultEvals", + "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsTrustworthiness", +] class ProjectUpdateParams(TypedDict, total=False): @@ -16,9 +28,276 @@ class ProjectUpdateParams(TypedDict, total=False): description: Optional[str] +class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): + criteria: Required[str] + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigCustomEvals(TypedDict, total=False): + evals: Dict[str, ConfigEvalConfigCustomEvalsEvals] + + +class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class ConfigEvalConfigDefaultEvals(TypedDict, total=False): + context_sufficiency: ConfigEvalConfigDefaultEvalsContextSufficiency + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + query_ease: ConfigEvalConfigDefaultEvalsQueryEase + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_groundedness: ConfigEvalConfigDefaultEvalsResponseGroundedness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_helpfulness: ConfigEvalConfigDefaultEvalsResponseHelpfulness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + trustworthiness: ConfigEvalConfigDefaultEvalsTrustworthiness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + +class ConfigEvalConfig(TypedDict, total=False): + custom_evals: ConfigEvalConfigCustomEvals + """Configuration for custom evaluation metrics.""" + + default_evals: ConfigEvalConfigDefaultEvals + """Configuration for default evaluation metrics.""" + + class Config(TypedDict, total=False): clustering_use_llm_matching: bool + eval_config: ConfigEvalConfig + """Configuration for project-specific evaluation metrics""" + llm_matching_model: str llm_matching_quality_preset: str diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 5c29fddf..d5e0e1cc 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -39,6 +39,72 @@ def test_method_create_with_all_params(self, client: Codex) -> None: project = client.projects.create( config={ "clustering_use_llm_matching": True, + "eval_config": { + "custom_evals": { + "evals": { + "foo": { + "criteria": "criteria", + "eval_key": "eval_key", + "name": "name", + "context_identifier": "context_identifier", + "enabled": True, + "priority": 0, + "query_identifier": "query_identifier", + "response_identifier": "response_identifier", + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + } + } + }, + "default_evals": { + "context_sufficiency": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "query_ease": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_groundedness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_helpfulness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "trustworthiness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + }, + }, "llm_matching_model": "llm_matching_model", "llm_matching_quality_preset": "llm_matching_quality_preset", "lower_llm_match_distance_threshold": 0, @@ -141,6 +207,72 @@ def test_method_update_with_all_params(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={ "clustering_use_llm_matching": True, + "eval_config": { + "custom_evals": { + "evals": { + "foo": { + "criteria": "criteria", + "eval_key": "eval_key", + "name": "name", + "context_identifier": "context_identifier", + "enabled": True, + "priority": 0, + "query_identifier": "query_identifier", + "response_identifier": "response_identifier", + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + } + } + }, + "default_evals": { + "context_sufficiency": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "query_ease": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_groundedness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_helpfulness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "trustworthiness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + }, + }, "llm_matching_model": "llm_matching_model", "llm_matching_quality_preset": "llm_matching_quality_preset", "lower_llm_match_distance_threshold": 0, @@ -534,6 +666,72 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> project = await async_client.projects.create( config={ "clustering_use_llm_matching": True, + "eval_config": { + "custom_evals": { + "evals": { + "foo": { + "criteria": "criteria", + "eval_key": "eval_key", + "name": "name", + "context_identifier": "context_identifier", + "enabled": True, + "priority": 0, + "query_identifier": "query_identifier", + "response_identifier": "response_identifier", + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + } + } + }, + "default_evals": { + "context_sufficiency": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "query_ease": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_groundedness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_helpfulness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "trustworthiness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + }, + }, "llm_matching_model": "llm_matching_model", "llm_matching_quality_preset": "llm_matching_quality_preset", "lower_llm_match_distance_threshold": 0, @@ -636,6 +834,72 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", config={ "clustering_use_llm_matching": True, + "eval_config": { + "custom_evals": { + "evals": { + "foo": { + "criteria": "criteria", + "eval_key": "eval_key", + "name": "name", + "context_identifier": "context_identifier", + "enabled": True, + "priority": 0, + "query_identifier": "query_identifier", + "response_identifier": "response_identifier", + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + } + } + }, + "default_evals": { + "context_sufficiency": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "query_ease": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_groundedness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_helpfulness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + "trustworthiness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "priority": 0, + "should_escalate": True, + "threshold": 0, + "threshold_direction": "above", + }, + }, + }, "llm_matching_model": "llm_matching_model", "llm_matching_quality_preset": "llm_matching_quality_preset", "lower_llm_match_distance_threshold": 0, From 0048a179ad99bf1f3ef3367c06788c0dc0c8b0ae Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 03:26:20 +0000 Subject: [PATCH 149/320] fix(docs/api): remove references to nonexistent types --- api.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api.md b/api.md index f3a2ea14..8aac76fd 100644 --- a/api.md +++ b/api.md @@ -139,8 +139,6 @@ from codex.types import ( ProjectReturnSchema, ProjectRetrieveResponse, ProjectListResponse, - ProjectExportResponse, - ProjectIncrementQueriesResponse, ProjectRetrieveAnalyticsResponse, ProjectValidateResponse, ) @@ -153,8 +151,8 @@ Methods: - client.projects.update(project_id, \*\*params) -> ProjectReturnSchema - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None -- client.projects.export(project_id) -> object -- client.projects.increment_queries(project_id, \*\*params) -> object +- client.projects.export(project_id) -> object +- client.projects.increment_queries(project_id, \*\*params) -> object - client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse - client.projects.validate(project_id, \*\*params) -> ProjectValidateResponse From f48542a4ebea2df63b7f1da0345bcebbe535fa8a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 03:09:34 +0000 Subject: [PATCH 150/320] chore(api): mark some methods as deprecated --- src/codex/resources/projects/entries.py | 27 ++- src/codex/resources/projects/projects.py | 27 ++- tests/api_resources/projects/test_entries.py | 164 ++++++++++--------- tests/api_resources/test_projects.py | 100 ++++++----- 4 files changed, 186 insertions(+), 132 deletions(-) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index 346dd353..c6b43a4a 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import Iterable, Optional import httpx @@ -312,6 +313,7 @@ def publish_draft_answer( cast_to=Entry, ) + @typing_extensions.deprecated("deprecated") def query( self, project_id: str, @@ -707,6 +709,7 @@ async def publish_draft_answer( cast_to=Entry, ) + @typing_extensions.deprecated("deprecated") async def query( self, project_id: str, @@ -839,8 +842,10 @@ def __init__(self, entries: EntriesResource) -> None: self.publish_draft_answer = to_raw_response_wrapper( entries.publish_draft_answer, ) - self.query = to_raw_response_wrapper( - entries.query, + self.query = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + entries.query # pyright: ignore[reportDeprecated], + ) ) self.unpublish_answer = to_raw_response_wrapper( entries.unpublish_answer, @@ -869,8 +874,10 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.publish_draft_answer = async_to_raw_response_wrapper( entries.publish_draft_answer, ) - self.query = async_to_raw_response_wrapper( - entries.query, + self.query = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + entries.query # pyright: ignore[reportDeprecated], + ) ) self.unpublish_answer = async_to_raw_response_wrapper( entries.unpublish_answer, @@ -899,8 +906,10 @@ def __init__(self, entries: EntriesResource) -> None: self.publish_draft_answer = to_streamed_response_wrapper( entries.publish_draft_answer, ) - self.query = to_streamed_response_wrapper( - entries.query, + self.query = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + entries.query # pyright: ignore[reportDeprecated], + ) ) self.unpublish_answer = to_streamed_response_wrapper( entries.unpublish_answer, @@ -929,8 +938,10 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.publish_draft_answer = async_to_streamed_response_wrapper( entries.publish_draft_answer, ) - self.query = async_to_streamed_response_wrapper( - entries.query, + self.query = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + entries.query # pyright: ignore[reportDeprecated], + ) ) self.unpublish_answer = async_to_streamed_response_wrapper( entries.unpublish_answer, diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 6195d1a4..cf8c0f82 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import Dict, List, Optional from typing_extensions import Literal @@ -330,6 +331,7 @@ def export( cast_to=object, ) + @typing_extensions.deprecated("deprecated") def increment_queries( self, project_id: str, @@ -872,6 +874,7 @@ async def export( cast_to=object, ) + @typing_extensions.deprecated("deprecated") async def increment_queries( self, project_id: str, @@ -1167,8 +1170,10 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_raw_response_wrapper( projects.export, ) - self.increment_queries = to_raw_response_wrapper( - projects.increment_queries, + self.increment_queries = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + projects.increment_queries # pyright: ignore[reportDeprecated], + ) ) self.retrieve_analytics = to_raw_response_wrapper( projects.retrieve_analytics, @@ -1212,8 +1217,10 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_raw_response_wrapper( projects.export, ) - self.increment_queries = async_to_raw_response_wrapper( - projects.increment_queries, + self.increment_queries = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + projects.increment_queries # pyright: ignore[reportDeprecated], + ) ) self.retrieve_analytics = async_to_raw_response_wrapper( projects.retrieve_analytics, @@ -1257,8 +1264,10 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_streamed_response_wrapper( projects.export, ) - self.increment_queries = to_streamed_response_wrapper( - projects.increment_queries, + self.increment_queries = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + projects.increment_queries # pyright: ignore[reportDeprecated], + ) ) self.retrieve_analytics = to_streamed_response_wrapper( projects.retrieve_analytics, @@ -1302,8 +1311,10 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_streamed_response_wrapper( projects.export, ) - self.increment_queries = async_to_streamed_response_wrapper( - projects.increment_queries, + self.increment_queries = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + projects.increment_queries # pyright: ignore[reportDeprecated], + ) ) self.retrieve_analytics = async_to_streamed_response_wrapper( projects.retrieve_analytics, diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 73a45ad4..32b0452e 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -15,6 +15,8 @@ EntryNotifySmeResponse, ) +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -382,40 +384,45 @@ def test_path_params_publish_draft_answer(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_query(self, client: Codex) -> None: - entry = client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) + with pytest.warns(DeprecationWarning): + entry = client.projects.entries.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize def test_method_query_with_all_params(self, client: Codex) -> None: - entry = client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - use_llm_matching=True, - client_metadata={}, - query_metadata={ - "context": "string", - "custom_metadata": {}, - "eval_scores": {"foo": 0}, - "evaluated_response": "evaluated_response", - }, - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) + with pytest.warns(DeprecationWarning): + entry = client.projects.entries.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + use_llm_matching=True, + client_metadata={}, + query_metadata={ + "context": "string", + "custom_metadata": {}, + "eval_scores": {"foo": 0}, + "evaluated_response": "evaluated_response", + }, + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize def test_raw_response_query(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.entries.with_raw_response.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -425,26 +432,28 @@ def test_raw_response_query(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_query(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.entries.with_streaming_response.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(EntryQueryResponse, entry, path=["response"]) + entry = response.parse() + assert_matches_type(EntryQueryResponse, entry, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize def test_path_params_query(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.query( - project_id="", - question="question", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.query( + project_id="", + question="question", + ) @pytest.mark.skip() @parametrize @@ -863,40 +872,45 @@ async def test_path_params_publish_draft_answer(self, async_client: AsyncCodex) @pytest.mark.skip() @parametrize async def test_method_query(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) + with pytest.warns(DeprecationWarning): + entry = await async_client.projects.entries.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - use_llm_matching=True, - client_metadata={}, - query_metadata={ - "context": "string", - "custom_metadata": {}, - "eval_scores": {"foo": 0}, - "evaluated_response": "evaluated_response", - }, - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) + with pytest.warns(DeprecationWarning): + entry = await async_client.projects.entries.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + use_llm_matching=True, + client_metadata={}, + query_metadata={ + "context": "string", + "custom_metadata": {}, + "eval_scores": {"foo": 0}, + "evaluated_response": "evaluated_response", + }, + x_client_library_version="x-client-library-version", + x_integration_type="x-integration-type", + x_source="x-source", + x_stainless_package_version="x-stainless-package-version", + ) + assert_matches_type(EntryQueryResponse, entry, path=["response"]) @pytest.mark.skip() @parametrize async def test_raw_response_query(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.entries.with_raw_response.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -906,26 +920,28 @@ async def test_raw_response_query(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.entries.with_streaming_response.query( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(EntryQueryResponse, entry, path=["response"]) + entry = await response.parse() + assert_matches_type(EntryQueryResponse, entry, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize async def test_path_params_query(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.query( - project_id="", - question="question", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.query( + project_id="", + question="question", + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index d5e0e1cc..f7c3f017 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -17,6 +17,8 @@ ) from tests.utils import assert_matches_type +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -454,26 +456,31 @@ def test_path_params_export(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_increment_queries(self, client: Codex) -> None: - project = client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + project = client.projects.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) @pytest.mark.skip() @parametrize def test_method_increment_queries_with_all_params(self, client: Codex) -> None: - project = client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - count=0, - ) + with pytest.warns(DeprecationWarning): + project = client.projects.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + count=0, + ) + assert_matches_type(object, project, path=["response"]) @pytest.mark.skip() @parametrize def test_raw_response_increment_queries(self, client: Codex) -> None: - response = client.projects.with_raw_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.with_raw_response.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -483,24 +490,26 @@ def test_raw_response_increment_queries(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_increment_queries(self, client: Codex) -> None: - with client.projects.with_streaming_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.with_streaming_response.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - project = response.parse() - assert_matches_type(object, project, path=["response"]) + project = response.parse() + assert_matches_type(object, project, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize def test_path_params_increment_queries(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.with_raw_response.increment_queries( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.increment_queries( + project_id="", + ) @pytest.mark.skip() @parametrize @@ -1081,26 +1090,31 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_method_increment_queries(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + project = await async_client.projects.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_increment_queries_with_all_params(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - count=0, - ) + with pytest.warns(DeprecationWarning): + project = await async_client.projects.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + count=0, + ) + assert_matches_type(object, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.with_raw_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.with_raw_response.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1110,24 +1124,26 @@ async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> @pytest.mark.skip() @parametrize async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None: - async with async_client.projects.with_streaming_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.with_streaming_response.increment_queries( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - project = await response.parse() - assert_matches_type(object, project, path=["response"]) + project = await response.parse() + assert_matches_type(object, project, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.with_raw_response.increment_queries( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.increment_queries( + project_id="", + ) @pytest.mark.skip() @parametrize From 5b5c3b0c992677e53c96dc8ffe44afba76888c1c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 17:16:51 +0000 Subject: [PATCH 151/320] feat(api): api update --- .stats.yml | 2 +- README.md | 1 + src/codex/types/project_create_params.py | 3 +++ src/codex/types/project_list_response.py | 3 +++ src/codex/types/project_retrieve_response.py | 3 +++ src/codex/types/project_return_schema.py | 3 +++ src/codex/types/project_update_params.py | 3 +++ tests/api_resources/test_projects.py | 4 ++++ 8 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index e80f0e13..ddf72408 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 0f1841fad65926e7ddfb22dd7a642b46 +openapi_spec_hash: dfccb5c181396678a22b9c079847889f config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/README.md b/README.md index 4663d2fd..43251e6e 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ project_return_schema = client.projects.create( "name": "name", "context_identifier": "context_identifier", "enabled": True, + "is_default": True, "priority": 0, "query_identifier": "query_identifier", "response_identifier": "response_identifier", diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index 75892e0b..31427557 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -55,6 +55,9 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + is_default: bool + """Whether the eval is a default, built-in eval or a custom eval""" + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 59d3bf81..d4805732 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -47,6 +47,9 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + is_default: Optional[bool] = None + """Whether the eval is a default, built-in eval or a custom eval""" + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index a631f0c2..fb62cff3 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -46,6 +46,9 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + is_default: Optional[bool] = None + """Whether the eval is a default, built-in eval or a custom eval""" + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 7da2e615..420ec6e7 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -46,6 +46,9 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + is_default: Optional[bool] = None + """Whether the eval is a default, built-in eval or a custom eval""" + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index d58dd591..d1999550 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -53,6 +53,9 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + is_default: bool + """Whether the eval is a default, built-in eval or a custom eval""" + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index f7c3f017..8ba69a14 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -50,6 +50,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "name": "name", "context_identifier": "context_identifier", "enabled": True, + "is_default": True, "priority": 0, "query_identifier": "query_identifier", "response_identifier": "response_identifier", @@ -218,6 +219,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "name": "name", "context_identifier": "context_identifier", "enabled": True, + "is_default": True, "priority": 0, "query_identifier": "query_identifier", "response_identifier": "response_identifier", @@ -684,6 +686,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "name": "name", "context_identifier": "context_identifier", "enabled": True, + "is_default": True, "priority": 0, "query_identifier": "query_identifier", "response_identifier": "response_identifier", @@ -852,6 +855,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "name": "name", "context_identifier": "context_identifier", "enabled": True, + "is_default": True, "priority": 0, "query_identifier": "query_identifier", "response_identifier": "response_identifier", From 01efedbbd10ae470fb13bb84fffb7e21a5f45885 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 02:20:36 +0000 Subject: [PATCH 152/320] chore(docs): remove reference to rye shell --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9fa9a13..548ff4c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix From 56f90646a781d4651c7d2cb3b1e65350d9e18371 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 02:32:47 +0000 Subject: [PATCH 153/320] chore(docs): remove unnecessary param examples --- README.md | 77 +------------------------------------------------------ 1 file changed, 1 insertion(+), 76 deletions(-) diff --git a/README.md b/README.md index 43251e6e..05a9dc97 100644 --- a/README.md +++ b/README.md @@ -155,82 +155,7 @@ from codex import Codex client = Codex() project_return_schema = client.projects.create( - config={ - "clustering_use_llm_matching": True, - "eval_config": { - "custom_evals": { - "evals": { - "foo": { - "criteria": "criteria", - "eval_key": "eval_key", - "name": "name", - "context_identifier": "context_identifier", - "enabled": True, - "is_default": True, - "priority": 0, - "query_identifier": "query_identifier", - "response_identifier": "response_identifier", - "should_escalate": True, - "threshold": 0, - "threshold_direction": "above", - } - } - }, - "default_evals": { - "context_sufficiency": { - "eval_key": "eval_key", - "name": "name", - "enabled": True, - "priority": 0, - "should_escalate": True, - "threshold": 0, - "threshold_direction": "above", - }, - "query_ease": { - "eval_key": "eval_key", - "name": "name", - "enabled": True, - "priority": 0, - "should_escalate": True, - "threshold": 0, - "threshold_direction": "above", - }, - "response_groundedness": { - "eval_key": "eval_key", - "name": "name", - "enabled": True, - "priority": 0, - "should_escalate": True, - "threshold": 0, - "threshold_direction": "above", - }, - "response_helpfulness": { - "eval_key": "eval_key", - "name": "name", - "enabled": True, - "priority": 0, - "should_escalate": True, - "threshold": 0, - "threshold_direction": "above", - }, - "trustworthiness": { - "eval_key": "eval_key", - "name": "name", - "enabled": True, - "priority": 0, - "should_escalate": True, - "threshold": 0, - "threshold_direction": "above", - }, - }, - }, - "llm_matching_model": "llm_matching_model", - "llm_matching_quality_preset": "llm_matching_quality_preset", - "lower_llm_match_distance_threshold": 0, - "max_distance": 0, - "query_use_llm_matching": True, - "upper_llm_match_distance_threshold": 0, - }, + config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) From d680d32f5667de458784b91d3f1d2ddde1fd28c2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 03:34:08 +0000 Subject: [PATCH 154/320] feat(client): add follow_redirects request option --- src/codex/_base_client.py | 6 +++++ src/codex/_models.py | 2 ++ src/codex/_types.py | 2 ++ tests/test_client.py | 54 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 0bf6ca7f..dff802e4 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -960,6 +960,9 @@ def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1460,6 +1463,9 @@ async def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None diff --git a/src/codex/_models.py b/src/codex/_models.py index 798956f1..4f214980 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): idempotency_key: str json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -750,6 +751,7 @@ 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 # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. diff --git a/src/codex/_types.py b/src/codex/_types.py index dfa51c2f..f2d17a1f 100644 --- a/src/codex/_types.py +++ b/src/codex/_types.py @@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted @@ -215,3 +216,4 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool diff --git a/tests/test_client.py b/tests/test_client.py index 2d356fac..bc381273 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -803,6 +803,33 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter) -> 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 = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> 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: + self.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 TestAsyncCodex: client = AsyncCodex(base_url=base_url, _strict_response_validation=True) @@ -1609,3 +1636,30 @@ async def test_main() -> None: raise AssertionError("calling get_platform using asyncify resulted in a hung process") time.sleep(0.1) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter) -> 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 self.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) -> 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 self.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" From e54ca45ab07da8e826c4c084385aeb3fbb4816d7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 22:17:18 +0000 Subject: [PATCH 155/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index ddf72408..145bcaa1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: dfccb5c181396678a22b9c079847889f +openapi_spec_hash: 12f575bae07a188ff130fdee07d46312 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 From 4ba26a2a2514252b745266eea17bc6d901205fd4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:17:31 +0000 Subject: [PATCH 156/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 145bcaa1..82cbfb62 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 12f575bae07a188ff130fdee07d46312 +openapi_spec_hash: a8387ccffe9a593cea310f37eb64ea0e config_hash: 659f65b6ccf5612986f920f7f9abbcb5 From 25d27632e4cdfb4ceaed75e91ba7049843ded17a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:17:08 +0000 Subject: [PATCH 157/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_create_params.py | 2 +- src/codex/types/project_list_response.py | 2 +- src/codex/types/project_retrieve_response.py | 2 +- src/codex/types/project_return_schema.py | 2 +- src/codex/types/project_update_params.py | 2 +- tests/api_resources/test_projects.py | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index 82cbfb62..0a2b18bb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: a8387ccffe9a593cea310f37eb64ea0e +openapi_spec_hash: ded11525d5fce121fac9be1f434c42af config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index 31427557..13338463 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -305,7 +305,7 @@ class Config(TypedDict, total=False): llm_matching_model: str - llm_matching_quality_preset: str + llm_matching_quality_preset: Literal["best", "high", "medium", "low", "base"] lower_llm_match_distance_threshold: float diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index d4805732..9051542a 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -297,7 +297,7 @@ class ProjectConfig(BaseModel): llm_matching_model: Optional[str] = None - llm_matching_quality_preset: Optional[str] = None + llm_matching_quality_preset: Optional[Literal["best", "high", "medium", "low", "base"]] = None lower_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index fb62cff3..5cf4f32b 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -296,7 +296,7 @@ class Config(BaseModel): llm_matching_model: Optional[str] = None - llm_matching_quality_preset: Optional[str] = None + llm_matching_quality_preset: Optional[Literal["best", "high", "medium", "low", "base"]] = None lower_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 420ec6e7..979be221 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -296,7 +296,7 @@ class Config(BaseModel): llm_matching_model: Optional[str] = None - llm_matching_quality_preset: Optional[str] = None + llm_matching_quality_preset: Optional[Literal["best", "high", "medium", "low", "base"]] = None lower_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index d1999550..6df16f43 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -303,7 +303,7 @@ class Config(TypedDict, total=False): llm_matching_model: str - llm_matching_quality_preset: str + llm_matching_quality_preset: Literal["best", "high", "medium", "low", "base"] lower_llm_match_distance_threshold: float diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 8ba69a14..7ca0ff9f 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -109,7 +109,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: }, }, "llm_matching_model": "llm_matching_model", - "llm_matching_quality_preset": "llm_matching_quality_preset", + "llm_matching_quality_preset": "best", "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, @@ -278,7 +278,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: }, }, "llm_matching_model": "llm_matching_model", - "llm_matching_quality_preset": "llm_matching_quality_preset", + "llm_matching_quality_preset": "best", "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, @@ -745,7 +745,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> }, }, "llm_matching_model": "llm_matching_model", - "llm_matching_quality_preset": "llm_matching_quality_preset", + "llm_matching_quality_preset": "best", "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, @@ -914,7 +914,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> }, }, "llm_matching_model": "llm_matching_model", - "llm_matching_quality_preset": "llm_matching_quality_preset", + "llm_matching_quality_preset": "best", "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, From 75e6c2516d58b78ff2701af66cec0a5c02e9e49d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:17:07 +0000 Subject: [PATCH 158/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 8 ++++++++ src/codex/types/project_create_params.py | 2 ++ src/codex/types/project_list_response.py | 2 ++ src/codex/types/project_retrieve_response.py | 2 ++ src/codex/types/project_return_schema.py | 2 ++ src/codex/types/project_update_params.py | 2 ++ tests/api_resources/test_projects.py | 4 ++++ 8 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 0a2b18bb..735d7e30 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: ded11525d5fce121fac9be1f434c42af +openapi_spec_hash: b342ef2514a2e44178169d241aff9273 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index cf8c0f82..5fc3428c 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -98,6 +98,7 @@ def create( config: project_create_params.Config, name: str, organization_id: str, + auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, description: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -125,6 +126,7 @@ def create( "config": config, "name": name, "organization_id": organization_id, + "auto_clustering_enabled": auto_clustering_enabled, "description": description, }, project_create_params.ProjectCreateParams, @@ -174,6 +176,7 @@ def update( *, config: project_update_params.Config, name: str, + auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, description: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -202,6 +205,7 @@ def update( { "config": config, "name": name, + "auto_clustering_enabled": auto_clustering_enabled, "description": description, }, project_update_params.ProjectUpdateParams, @@ -641,6 +645,7 @@ async def create( config: project_create_params.Config, name: str, organization_id: str, + auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, description: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -668,6 +673,7 @@ async def create( "config": config, "name": name, "organization_id": organization_id, + "auto_clustering_enabled": auto_clustering_enabled, "description": description, }, project_create_params.ProjectCreateParams, @@ -717,6 +723,7 @@ async def update( *, config: project_update_params.Config, name: str, + auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, description: Optional[str] | NotGiven = NOT_GIVEN, # 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. @@ -745,6 +752,7 @@ async def update( { "config": config, "name": name, + "auto_clustering_enabled": auto_clustering_enabled, "description": description, }, project_update_params.ProjectUpdateParams, diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index 13338463..4d9d31f5 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -27,6 +27,8 @@ class ProjectCreateParams(TypedDict, total=False): organization_id: Required[str] + auto_clustering_enabled: bool + description: Optional[str] diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 9051542a..465ed157 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -323,6 +323,8 @@ class Project(BaseModel): updated_at: datetime + auto_clustering_enabled: Optional[bool] = None + description: Optional[str] = None unanswered_entries_count: Optional[int] = None diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 5cf4f32b..53624096 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -322,6 +322,8 @@ class ProjectRetrieveResponse(BaseModel): updated_at: datetime + auto_clustering_enabled: Optional[bool] = None + custom_rank_enabled: Optional[bool] = None description: Optional[str] = None diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 979be221..2ad7433a 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -322,4 +322,6 @@ class ProjectReturnSchema(BaseModel): updated_at: datetime + auto_clustering_enabled: Optional[bool] = None + description: Optional[str] = None diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 6df16f43..fc6c52d4 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -25,6 +25,8 @@ class ProjectUpdateParams(TypedDict, total=False): name: Required[str] + auto_clustering_enabled: bool + description: Optional[str] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 7ca0ff9f..4c95e587 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -117,6 +117,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: }, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auto_clustering_enabled=True, description="description", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -285,6 +286,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "upper_llm_match_distance_threshold": 0, }, name="name", + auto_clustering_enabled=True, description="description", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -753,6 +755,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> }, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auto_clustering_enabled=True, description="description", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -921,6 +924,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "upper_llm_match_distance_threshold": 0, }, name="name", + auto_clustering_enabled=True, description="description", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) From d9358ed4031ddb58d1f37955855d7841a0a9bcc1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 02:06:17 +0000 Subject: [PATCH 159/320] chore(tests): run tests in parallel --- pyproject.toml | 3 ++- requirements-dev.lock | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8a5a62c7..68924033 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "rich>=13.7.1", "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -125,7 +126,7 @@ replacement = '[\1](https://github.com/cleanlab/codex-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" diff --git a/requirements-dev.lock b/requirements-dev.lock index 7fcfe657..9e127b74 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -30,6 +30,8 @@ distro==1.8.0 exceptiongroup==1.2.2 # via anyio # via pytest +execnet==2.1.1 + # via pytest-xdist filelock==3.12.4 # via virtualenv h11==0.14.0 @@ -72,7 +74,9 @@ pygments==2.18.0 pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio + # via pytest-xdist pytest-asyncio==0.24.0 +pytest-xdist==3.7.0 python-dateutil==2.8.2 # via time-machine pytz==2023.3.post1 From 35ff0fdfcdbed3ab925b9aa8abb44530d127f2ac Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 02:32:12 +0000 Subject: [PATCH 160/320] fix(client): correctly parse binary response | stream --- src/codex/_base_client.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index dff802e4..6ef7dd5f 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -1071,7 +1071,14 @@ def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1574,7 +1581,14 @@ async def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") From f254fb4d0c15230a8b6748a000f3db5d3fc0d558 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 18:17:09 +0000 Subject: [PATCH 161/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 735d7e30..c98d193e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: b342ef2514a2e44178169d241aff9273 +openapi_spec_hash: 392a7547e79deafa47b8c452f0f5b79e config_hash: 659f65b6ccf5612986f920f7f9abbcb5 From 0097703d30c4030e4a03bdf7ed005ddcb7e1353b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 22:17:08 +0000 Subject: [PATCH 162/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 8 ++++---- src/codex/types/project_list_params.py | 2 +- src/codex/types/project_list_response.py | 2 +- tests/api_resources/test_projects.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index c98d193e..09b48e62 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 392a7547e79deafa47b8c452f0f5b79e +openapi_spec_hash: 66a1eb040f577f552228baf4ddbd17d0 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 5fc3428c..b048b284 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -219,7 +219,7 @@ def update( def list( self, *, - include_entry_counts: bool | NotGiven = NOT_GIVEN, + include_unaddressed_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, @@ -254,7 +254,7 @@ def list( timeout=timeout, query=maybe_transform( { - "include_entry_counts": include_entry_counts, + "include_unaddressed_counts": include_unaddressed_counts, "limit": limit, "offset": offset, "order": order, @@ -766,7 +766,7 @@ async def update( async def list( self, *, - include_entry_counts: bool | NotGiven = NOT_GIVEN, + include_unaddressed_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, @@ -801,7 +801,7 @@ async def list( timeout=timeout, query=await async_maybe_transform( { - "include_entry_counts": include_entry_counts, + "include_unaddressed_counts": include_unaddressed_counts, "limit": limit, "offset": offset, "order": order, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index 0ab3b84b..0c4ec1bd 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -9,7 +9,7 @@ class ProjectListParams(TypedDict, total=False): - include_entry_counts: bool + include_unaddressed_counts: bool limit: int diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 465ed157..a7df3f38 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -327,7 +327,7 @@ class Project(BaseModel): description: Optional[str] = None - unanswered_entries_count: Optional[int] = None + unaddressed_count: Optional[int] = None class ProjectListResponse(BaseModel): diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 4c95e587..cf6b3171 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -341,7 +341,7 @@ def test_method_list(self, client: Codex) -> None: @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( - include_entry_counts=True, + include_unaddressed_counts=True, limit=1, offset=0, order="asc", @@ -979,7 +979,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( - include_entry_counts=True, + include_unaddressed_counts=True, limit=1, offset=0, order="asc", From 20c15ed6c46988c5ac3c35dfb99e036e146b68c3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 02:34:19 +0000 Subject: [PATCH 163/320] chore(tests): add tests for httpx client instantiation & proxies --- tests/test_client.py | 53 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index bc381273..d5815d3b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -27,7 +27,14 @@ from codex._models import BaseModel, FinalRequestOptions from codex._constants import RAW_RESPONSE_HEADER from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError -from codex._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options +from codex._base_client import ( + DEFAULT_TIMEOUT, + HTTPX_DEFAULT_TIMEOUT, + BaseClient, + DefaultHttpxClient, + DefaultAsyncHttpxClient, + make_request_options, +) from codex.types.project_create_params import ProjectCreateParams from .utils import update_env @@ -803,6 +810,28 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + 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) -> None: # Test that the default follow_redirects=True allows following redirects @@ -1637,6 +1666,28 @@ async def test_main() -> None: time.sleep(0.1) + 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") + + 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) -> None: # Test that the default follow_redirects=True allows following redirects From 52a5afd3e98d6f61f89e0b0c4caf67ba88c4292e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 04:04:00 +0000 Subject: [PATCH 164/320] chore(internal): update conftest.py --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index c93053b1..05c77729 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os From 225d0b63ef2fef2836323d2e511f5013ca0f7ce6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 06:33:35 +0000 Subject: [PATCH 165/320] chore(ci): enable for pull requests --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c02247a3..2f7778ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: From 488e5d1d114b35cab73d6ad56dd783ddc3bea0b8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 02:08:16 +0000 Subject: [PATCH 166/320] chore(readme): update badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05a9dc97..a3b3288d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Codex SDK API library -[![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg)](https://pypi.org/project/codex-sdk/) +[![PyPI version]()](https://pypi.org/project/codex-sdk/) The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, From 94d3c8533085aa0f2f9551d4202cb815cc07cd25 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:44:40 +0000 Subject: [PATCH 167/320] fix(tests): fix: tests which call HTTP endpoints directly with the example parameters --- tests/test_client.py | 73 ++++++++++---------------------------------- 1 file changed, 16 insertions(+), 57 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index d5815d3b..9cf7e943 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,9 +23,7 @@ from codex import Codex, AsyncCodex, APIResponseValidationError from codex._types import Omit -from codex._utils import maybe_transform from codex._models import BaseModel, FinalRequestOptions -from codex._constants import RAW_RESPONSE_HEADER from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError from codex._base_client import ( DEFAULT_TIMEOUT, @@ -35,7 +33,6 @@ DefaultAsyncHttpxClient, make_request_options, ) -from codex.types.project_create_params import ProjectCreateParams from .utils import update_env @@ -683,44 +680,25 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("codex._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: Codex) -> None: respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/api/projects/", - body=cast( - object, - maybe_transform( - dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), - ProjectCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.projects.with_streaming_response.create( + config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" + ).__enter__() assert _get_open_connections(self.client) == 0 @mock.patch("codex._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: Codex) -> None: respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/api/projects/", - body=cast( - object, - maybe_transform( - dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), - ProjectCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + client.projects.with_streaming_response.create( + config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" + ).__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1489,44 +1467,25 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("codex._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: AsyncCodex) -> None: respx_mock.post("/api/projects/").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/api/projects/", - body=cast( - object, - maybe_transform( - dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), - ProjectCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + await async_client.projects.with_streaming_response.create( + config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" + ).__aenter__() assert _get_open_connections(self.client) == 0 @mock.patch("codex._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: AsyncCodex) -> None: respx_mock.post("/api/projects/").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/api/projects/", - body=cast( - object, - maybe_transform( - dict(config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), - ProjectCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + await async_client.projects.with_streaming_response.create( + config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" + ).__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) From 62ccb8f1269b2f38a250c71597b570cdad40fe7f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 18:17:12 +0000 Subject: [PATCH 168/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/organization_list_members_response.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 09b48e62..40a2bb70 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 66a1eb040f577f552228baf4ddbd17d0 +openapi_spec_hash: 947c1396f52c4f78645b067ac0b708e5 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/types/organization_list_members_response.py b/src/codex/types/organization_list_members_response.py index 37897d56..1fa593ea 100644 --- a/src/codex/types/organization_list_members_response.py +++ b/src/codex/types/organization_list_members_response.py @@ -13,5 +13,7 @@ class OrganizationListMembersResponseItem(BaseModel): name: str + user_id: str + OrganizationListMembersResponse: TypeAlias = List[OrganizationListMembersResponseItem] From 13c1d7619e23cbb7d0d34d5a085ffe2977339349 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 02:47:46 +0000 Subject: [PATCH 169/320] docs(client): fix httpx.Timeout documentation reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3b3288d..ae489b6e 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ client.with_options(max_retries=5).projects.create( ### 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 codex import Codex From 8d6abb80de088c126aec035d2b0b4425d4423e51 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 20:17:22 +0000 Subject: [PATCH 170/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 96 +++--- src/codex/resources/tlm.py | 164 +++++------ src/codex/types/project_validate_params.py | 278 ++++++++++++++++-- .../types/projects/entry_query_params.py | 243 ++++++++++++++- src/codex/types/tlm_prompt_params.py | 41 ++- src/codex/types/tlm_score_params.py | 41 ++- tests/api_resources/projects/test_entries.py | 16 + tests/api_resources/test_projects.py | 14 + 9 files changed, 690 insertions(+), 205 deletions(-) diff --git a/.stats.yml b/.stats.yml index 40a2bb70..9317ce1b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 44 -openapi_spec_hash: 947c1396f52c4f78645b067ac0b708e5 +openapi_spec_hash: eeb8ebc5600523bdfad046381a929572 config_hash: 659f65b6ccf5612986f920f7f9abbcb5 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index b048b284..11b95d4d 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -3,7 +3,7 @@ from __future__ import annotations import typing_extensions -from typing import Dict, List, Optional +from typing import Dict, List, Iterable, Optional from typing_extensions import Literal import httpx @@ -436,6 +436,7 @@ def validate( custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, + messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, @@ -465,6 +466,10 @@ def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. + messages: Optional message history to provide conversation context for the query. Used to + rewrite query into a self-contained version of itself. If not provided, the + query will be treated as self-contained. + options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected (learn about quality presets in the TLM [initialization method](./#class-tlm)). @@ -490,27 +495,24 @@ def validate( `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -536,7 +538,7 @@ def validate( similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. @@ -588,6 +590,7 @@ def validate( "custom_eval_thresholds": custom_eval_thresholds, "custom_metadata": custom_metadata, "eval_scores": eval_scores, + "messages": messages, "options": options, "quality_preset": quality_preset, "task": task, @@ -985,6 +988,7 @@ async def validate( custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, + messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, @@ -1014,6 +1018,10 @@ async def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. + messages: Optional message history to provide conversation context for the query. Used to + rewrite query into a self-contained version of itself. If not provided, the + query will be treated as self-contained. + options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected (learn about quality presets in the TLM [initialization method](./#class-tlm)). @@ -1039,27 +1047,24 @@ async def validate( `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -1085,7 +1090,7 @@ async def validate( similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. @@ -1137,6 +1142,7 @@ async def validate( "custom_eval_thresholds": custom_eval_thresholds, "custom_metadata": custom_metadata, "eval_scores": eval_scores, + "messages": messages, "options": options, "quality_preset": quality_preset, "task": task, diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 5e4cd7e1..12ff6c0d 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -89,27 +89,24 @@ def prompt( `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -135,7 +132,7 @@ def prompt( similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. @@ -230,27 +227,24 @@ def score( `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -276,7 +270,7 @@ def score( similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. @@ -387,27 +381,24 @@ async def prompt( `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -433,7 +424,7 @@ async def prompt( similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. @@ -528,27 +519,24 @@ async def score( `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -574,7 +562,7 @@ async def score( similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index a855aa6f..8b38ebfa 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -2,12 +2,40 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing import Dict, List, Union, Iterable, Optional +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict from .._utils import PropertyInfo -__all__ = ["ProjectValidateParams", "Options"] +__all__ = [ + "ProjectValidateParams", + "Message", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParam", + "MessageChatCompletionUserMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamContentUnionMember1File", + "MessageChatCompletionUserMessageParamContentUnionMember1FileFile", + "MessageChatCompletionAssistantMessageParam", + "MessageChatCompletionAssistantMessageParamAudio", + "MessageChatCompletionAssistantMessageParamContentUnionMember1", + "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam", + "MessageChatCompletionAssistantMessageParamFunctionCall", + "MessageChatCompletionAssistantMessageParamToolCall", + "MessageChatCompletionAssistantMessageParamToolCallFunction", + "MessageChatCompletionToolMessageParam", + "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionFunctionMessageParam", + "Options", +] class ProjectValidateParams(TypedDict, total=False): @@ -38,6 +66,13 @@ class ProjectValidateParams(TypedDict, total=False): If not provided, TLM will be used to generate scores. """ + messages: Optional[Iterable[Message]] + """Optional message history to provide conversation context for the query. + + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. + """ + options: Optional[Options] """ Typed dict of advanced configuration options for the Trustworthy Language Model. @@ -65,27 +100,24 @@ class ProjectValidateParams(TypedDict, total=False): `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -111,7 +143,7 @@ class ProjectValidateParams(TypedDict, total=False): similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. @@ -143,6 +175,202 @@ class ProjectValidateParams(TypedDict, total=False): x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]] + + role: Required[Literal["developer"]] + + name: str + + +class MessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionSystemMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionSystemMessageParamContentUnionMember1]]] + + role: Required[Literal["system"]] + + name: str + + +class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam( + TypedDict, total=False +): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL( + TypedDict, total=False +): + url: Required[str] + + detail: Literal["auto", "low", "high"] + + +class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam( + TypedDict, total=False +): + image_url: Required[ + MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL + ] + + type: Required[Literal["image_url"]] + + +class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + TypedDict, total=False +): + data: Required[str] + + format: Required[Literal["wav", "mp3"]] + + +class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam( + TypedDict, total=False +): + input_audio: Required[ + MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + ] + + type: Required[Literal["input_audio"]] + + +class MessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False): + file_data: str + + file_id: str + + filename: str + + +class MessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False): + file: Required[MessageChatCompletionUserMessageParamContentUnionMember1FileFile] + + type: Required[Literal["file"]] + + +MessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam, + MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam, + MessageChatCompletionUserMessageParamContentUnionMember1File, +] + + +class MessageChatCompletionUserMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionUserMessageParamContentUnionMember1]]] + + role: Required[Literal["user"]] + + name: str + + +class MessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False): + id: Required[str] + + +class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam( + TypedDict, total=False +): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam( + TypedDict, total=False +): + refusal: Required[str] + + type: Required[Literal["refusal"]] + + +MessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class MessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class MessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False): + id: Required[str] + + function: Required[MessageChatCompletionAssistantMessageParamToolCallFunction] + + type: Required[Literal["function"]] + + +class MessageChatCompletionAssistantMessageParam(TypedDict, total=False): + role: Required[Literal["assistant"]] + + audio: Optional[MessageChatCompletionAssistantMessageParamAudio] + + content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamContentUnionMember1], None] + + function_call: Optional[MessageChatCompletionAssistantMessageParamFunctionCall] + + name: str + + refusal: Optional[str] + + tool_calls: Iterable[MessageChatCompletionAssistantMessageParamToolCall] + + +class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionToolMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionToolMessageParamContentUnionMember1]]] + + role: Required[Literal["tool"]] + + tool_call_id: Required[str] + + +class MessageChatCompletionFunctionMessageParam(TypedDict, total=False): + content: Required[Optional[str]] + + name: Required[str] + + role: Required[Literal["function"]] + + +Message: TypeAlias = Union[ + MessageChatCompletionDeveloperMessageParam, + MessageChatCompletionSystemMessageParam, + MessageChatCompletionUserMessageParam, + MessageChatCompletionAssistantMessageParam, + MessageChatCompletionToolMessageParam, + MessageChatCompletionFunctionMessageParam, +] + + class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index 1edabbed..2ba33b82 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -3,11 +3,40 @@ from __future__ import annotations from typing import Dict, List, Union, Iterable, Optional -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict from ..._utils import PropertyInfo -__all__ = ["EntryQueryParams", "QueryMetadata", "QueryMetadataContextUnionMember3"] +__all__ = [ + "EntryQueryParams", + "QueryMetadata", + "QueryMetadataContextUnionMember3", + "QueryMetadataMessage", + "QueryMetadataMessageChatCompletionDeveloperMessageParam", + "QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1", + "QueryMetadataMessageChatCompletionSystemMessageParam", + "QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1", + "QueryMetadataMessageChatCompletionUserMessageParam", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File", + "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile", + "QueryMetadataMessageChatCompletionAssistantMessageParam", + "QueryMetadataMessageChatCompletionAssistantMessageParamAudio", + "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1", + "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam", + "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam", + "QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall", + "QueryMetadataMessageChatCompletionAssistantMessageParamToolCall", + "QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction", + "QueryMetadataMessageChatCompletionToolMessageParam", + "QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1", + "QueryMetadataMessageChatCompletionFunctionMessageParam", +] class EntryQueryParams(TypedDict, total=False): @@ -47,6 +76,202 @@ class QueryMetadataContextUnionMember3(TypedDict, total=False): """Title or heading of the document. Useful for display and context.""" +class QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class QueryMetadataMessageChatCompletionDeveloperMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1]]] + + role: Required[Literal["developer"]] + + name: str + + +class QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class QueryMetadataMessageChatCompletionSystemMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1]]] + + role: Required[Literal["system"]] + + name: str + + +class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam( + TypedDict, total=False +): + text: Required[str] + + type: Required[Literal["text"]] + + +class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL( + TypedDict, total=False +): + url: Required[str] + + detail: Literal["auto", "low", "high"] + + +class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam( + TypedDict, total=False +): + image_url: Required[ + QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL + ] + + type: Required[Literal["image_url"]] + + +class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + TypedDict, total=False +): + data: Required[str] + + format: Required[Literal["wav", "mp3"]] + + +class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam( + TypedDict, total=False +): + input_audio: Required[ + QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + ] + + type: Required[Literal["input_audio"]] + + +class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False): + file_data: str + + file_id: str + + filename: str + + +class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False): + file: Required[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile] + + type: Required[Literal["file"]] + + +QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[ + QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam, + QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam, + QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam, + QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File, +] + + +class QueryMetadataMessageChatCompletionUserMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1]]] + + role: Required[Literal["user"]] + + name: str + + +class QueryMetadataMessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False): + id: Required[str] + + +class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam( + TypedDict, total=False +): + text: Required[str] + + type: Required[Literal["text"]] + + +class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam( + TypedDict, total=False +): + refusal: Required[str] + + type: Required[Literal["refusal"]] + + +QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[ + QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam, + QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class QueryMetadataMessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False): + id: Required[str] + + function: Required[QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction] + + type: Required[Literal["function"]] + + +class QueryMetadataMessageChatCompletionAssistantMessageParam(TypedDict, total=False): + role: Required[Literal["assistant"]] + + audio: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamAudio] + + content: Union[str, Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1], None] + + function_call: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall] + + name: str + + refusal: Optional[str] + + tool_calls: Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamToolCall] + + +class QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class QueryMetadataMessageChatCompletionToolMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1]]] + + role: Required[Literal["tool"]] + + tool_call_id: Required[str] + + +class QueryMetadataMessageChatCompletionFunctionMessageParam(TypedDict, total=False): + content: Required[Optional[str]] + + name: Required[str] + + role: Required[Literal["function"]] + + +QueryMetadataMessage: TypeAlias = Union[ + QueryMetadataMessageChatCompletionDeveloperMessageParam, + QueryMetadataMessageChatCompletionSystemMessageParam, + QueryMetadataMessageChatCompletionUserMessageParam, + QueryMetadataMessageChatCompletionAssistantMessageParam, + QueryMetadataMessageChatCompletionToolMessageParam, + QueryMetadataMessageChatCompletionFunctionMessageParam, +] + + class QueryMetadata(TypedDict, total=False): context: Union[str, List[str], Iterable[object], Iterable[QueryMetadataContextUnionMember3], None] """RAG context used for the query""" @@ -59,3 +284,17 @@ class QueryMetadata(TypedDict, total=False): evaluated_response: Optional[str] """The response being evaluated from the RAG system(before any remediation)""" + + messages: Optional[Iterable[QueryMetadataMessage]] + """Optional message history to provide conversation context for the query. + + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. + """ + + original_question: Optional[str] + """The original question that was asked before any rewriting or processing. + + For all non-conversational RAG, original_question should be the same as the + final question seen in Codex. + """ diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 94536055..3c04bfc4 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -40,27 +40,24 @@ class TlmPromptParams(TypedDict, total=False): `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -86,7 +83,7 @@ class TlmPromptParams(TypedDict, total=False): similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index a0d90175..95bcc4c4 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -42,27 +42,24 @@ class TlmScoreParams(TypedDict, total=False): `use_self_reflection` = True. - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a cheaper self-reflection will be used to compute the - trustworthiness score. - - By default, the TLM uses the "medium" quality preset. The default base LLM - `model` used is "gpt-4o-mini", and `max_tokens` is 512 for all quality presets. - You can set custom values for these arguments regardless of the quality preset - specified. - - Args: model ({"gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-4.5-preview", "gpt-3.5-turbo-16k", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", - "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = - "gpt-4o-mini"): Underlying base LLM to use (better models yield better results, - faster models yield faster/cheaper results). - Models still in beta: "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", - "gpt-4.5-preview", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-haiku", "nova-micro", "nova-lite", "nova-pro". - Recommended models - for accuracy: "gpt-4.1", "o4-mini", "o3", "claude-3.7-sonnet", - "claude-3.5-sonnet-v2". - Recommended models for low latency/costs: - "gpt-4.1-nano", "nova-micro". + "base" preset, a faster self-reflection is employed. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", + "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", + "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", + "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", + "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, + default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield + better results, faster models yield faster results). - Models still in beta: + "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", + "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", + "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low + latency/costs: "gpt-4.1-nano", "nova-micro". max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. @@ -88,7 +85,7 @@ class TlmScoreParams(TypedDict, total=False): similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include: "semantic" (based on natural language inference), + Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 32b0452e..eb6fd372 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -406,6 +406,14 @@ def test_method_query_with_all_params(self, client: Codex) -> None: "custom_metadata": {}, "eval_scores": {"foo": 0}, "evaluated_response": "evaluated_response", + "messages": [ + { + "content": "string", + "role": "developer", + "name": "name", + } + ], + "original_question": "original_question", }, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -894,6 +902,14 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N "custom_metadata": {}, "eval_scores": {"foo": 0}, "evaluated_response": "evaluated_response", + "messages": [ + { + "content": "string", + "role": "developer", + "name": "name", + } + ], + "original_question": "original_question", }, x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index cf6b3171..c2b5b7db 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -593,6 +593,13 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: custom_eval_thresholds={"foo": 0}, custom_metadata={}, eval_scores={"foo": 0}, + messages=[ + { + "content": "string", + "role": "developer", + "name": "name", + } + ], options={ "custom_eval_criteria": [{}], "log": ["string"], @@ -1231,6 +1238,13 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - custom_eval_thresholds={"foo": 0}, custom_metadata={}, eval_scores={"foo": 0}, + messages=[ + { + "content": "string", + "role": "developer", + "name": "name", + } + ], options={ "custom_eval_criteria": [{}], "log": ["string"], From 63b8d21601addfa9b8c81e5d53a1841fb1e6214f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 23:30:22 +0000 Subject: [PATCH 171/320] feat(api): add new endpoints --- .stats.yml | 4 +- api.md | 72 + src/codex/resources/projects/__init__.py | 42 + src/codex/resources/projects/evals.py | 803 +++++++++++ src/codex/resources/projects/projects.py | 202 +++ src/codex/resources/projects/query_logs.py | 741 ++++++++++ src/codex/resources/projects/remediations.py | 1135 +++++++++++++++ src/codex/types/__init__.py | 2 + src/codex/types/project_invite_sme_params.py | 15 + .../types/project_invite_sme_response.py | 11 + src/codex/types/projects/__init__.py | 31 + .../types/projects/eval_create_params.py | 66 + .../types/projects/eval_list_response.py | 69 + .../types/projects/eval_update_params.py | 104 ++ .../query_log_list_by_group_params.py | 41 + .../query_log_list_by_group_response.py | 96 ++ .../projects/query_log_list_groups_params.py | 38 + .../query_log_list_groups_response.py | 91 ++ .../types/projects/query_log_list_params.py | 38 + .../types/projects/query_log_list_response.py | 87 ++ .../projects/query_log_retrieve_response.py | 79 ++ .../query_log_start_remediation_response.py | 33 + .../projects/remediation_create_params.py | 16 + .../projects/remediation_create_response.py | 33 + .../remediation_edit_answer_params.py | 13 + .../remediation_edit_answer_response.py | 33 + .../remediation_edit_draft_answer_params.py | 13 + .../remediation_edit_draft_answer_response.py | 33 + ...iation_get_resolved_logs_count_response.py | 35 + .../types/projects/remediation_list_params.py | 39 + ...remediation_list_resolved_logs_response.py | 85 ++ .../projects/remediation_list_response.py | 41 + .../projects/remediation_pause_response.py | 33 + .../projects/remediation_publish_response.py | 33 + .../projects/remediation_retrieve_response.py | 33 + .../projects/remediation_unpause_response.py | 33 + tests/api_resources/projects/test_evals.py | 679 +++++++++ .../api_resources/projects/test_query_logs.py | 593 ++++++++ .../projects/test_remediations.py | 1224 +++++++++++++++++ tests/api_resources/test_projects.py | 109 ++ 40 files changed, 6876 insertions(+), 2 deletions(-) create mode 100644 src/codex/resources/projects/evals.py create mode 100644 src/codex/resources/projects/query_logs.py create mode 100644 src/codex/resources/projects/remediations.py create mode 100644 src/codex/types/project_invite_sme_params.py create mode 100644 src/codex/types/project_invite_sme_response.py create mode 100644 src/codex/types/projects/eval_create_params.py create mode 100644 src/codex/types/projects/eval_list_response.py create mode 100644 src/codex/types/projects/eval_update_params.py create mode 100644 src/codex/types/projects/query_log_list_by_group_params.py create mode 100644 src/codex/types/projects/query_log_list_by_group_response.py create mode 100644 src/codex/types/projects/query_log_list_groups_params.py create mode 100644 src/codex/types/projects/query_log_list_groups_response.py create mode 100644 src/codex/types/projects/query_log_list_params.py create mode 100644 src/codex/types/projects/query_log_list_response.py create mode 100644 src/codex/types/projects/query_log_retrieve_response.py create mode 100644 src/codex/types/projects/query_log_start_remediation_response.py create mode 100644 src/codex/types/projects/remediation_create_params.py create mode 100644 src/codex/types/projects/remediation_create_response.py create mode 100644 src/codex/types/projects/remediation_edit_answer_params.py create mode 100644 src/codex/types/projects/remediation_edit_answer_response.py create mode 100644 src/codex/types/projects/remediation_edit_draft_answer_params.py create mode 100644 src/codex/types/projects/remediation_edit_draft_answer_response.py create mode 100644 src/codex/types/projects/remediation_get_resolved_logs_count_response.py create mode 100644 src/codex/types/projects/remediation_list_params.py create mode 100644 src/codex/types/projects/remediation_list_resolved_logs_response.py create mode 100644 src/codex/types/projects/remediation_list_response.py create mode 100644 src/codex/types/projects/remediation_pause_response.py create mode 100644 src/codex/types/projects/remediation_publish_response.py create mode 100644 src/codex/types/projects/remediation_retrieve_response.py create mode 100644 src/codex/types/projects/remediation_unpause_response.py create mode 100644 tests/api_resources/projects/test_evals.py create mode 100644 tests/api_resources/projects/test_query_logs.py create mode 100644 tests/api_resources/projects/test_remediations.py diff --git a/.stats.yml b/.stats.yml index 9317ce1b..bd77f787 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 44 +configured_endpoints: 65 openapi_spec_hash: eeb8ebc5600523bdfad046381a929572 -config_hash: 659f65b6ccf5612986f920f7f9abbcb5 +config_hash: 63e520502003839482d0dbeb82132064 diff --git a/api.md b/api.md index 8aac76fd..13e82147 100644 --- a/api.md +++ b/api.md @@ -139,6 +139,7 @@ from codex.types import ( ProjectReturnSchema, ProjectRetrieveResponse, ProjectListResponse, + ProjectInviteSmeResponse, ProjectRetrieveAnalyticsResponse, ProjectValidateResponse, ) @@ -153,6 +154,7 @@ Methods: - client.projects.delete(project_id) -> None - client.projects.export(project_id) -> object - client.projects.increment_queries(project_id, \*\*params) -> object +- client.projects.invite_sme(project_id, \*\*params) -> ProjectInviteSmeResponse - client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse - client.projects.validate(project_id, \*\*params) -> ProjectValidateResponse @@ -210,6 +212,76 @@ Methods: - client.projects.clusters.list(project_id, \*\*params) -> SyncOffsetPageClusters[ClusterListResponse] - client.projects.clusters.list_variants(representative_entry_id, \*, project_id) -> ClusterListVariantsResponse +## Evals + +Types: + +```python +from codex.types.projects import EvalListResponse +``` + +Methods: + +- client.projects.evals.create(project_id, \*\*params) -> ProjectReturnSchema +- client.projects.evals.update(path_eval_key, \*, project_id, \*\*params) -> ProjectReturnSchema +- client.projects.evals.list(project_id) -> EvalListResponse +- client.projects.evals.delete(eval_key, \*, project_id) -> ProjectReturnSchema + +## QueryLogs + +Types: + +```python +from codex.types.projects import ( + QueryLogRetrieveResponse, + QueryLogListResponse, + QueryLogListByGroupResponse, + QueryLogListGroupsResponse, + QueryLogStartRemediationResponse, +) +``` + +Methods: + +- client.projects.query_logs.retrieve(query_log_id, \*, project_id) -> QueryLogRetrieveResponse +- client.projects.query_logs.list(project_id, \*\*params) -> QueryLogListResponse +- client.projects.query_logs.list_by_group(project_id, \*\*params) -> QueryLogListByGroupResponse +- client.projects.query_logs.list_groups(project_id, \*\*params) -> QueryLogListGroupsResponse +- client.projects.query_logs.start_remediation(query_log_id, \*, project_id) -> QueryLogStartRemediationResponse + +## Remediations + +Types: + +```python +from codex.types.projects import ( + RemediationCreateResponse, + RemediationRetrieveResponse, + RemediationListResponse, + RemediationEditAnswerResponse, + RemediationEditDraftAnswerResponse, + RemediationGetResolvedLogsCountResponse, + RemediationListResolvedLogsResponse, + RemediationPauseResponse, + RemediationPublishResponse, + RemediationUnpauseResponse, +) +``` + +Methods: + +- client.projects.remediations.create(project_id, \*\*params) -> RemediationCreateResponse +- client.projects.remediations.retrieve(remediation_id, \*, project_id) -> RemediationRetrieveResponse +- client.projects.remediations.list(project_id, \*\*params) -> RemediationListResponse +- client.projects.remediations.delete(remediation_id, \*, project_id) -> None +- client.projects.remediations.edit_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditAnswerResponse +- client.projects.remediations.edit_draft_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditDraftAnswerResponse +- client.projects.remediations.get_resolved_logs_count(remediation_id, \*, project_id) -> RemediationGetResolvedLogsCountResponse +- client.projects.remediations.list_resolved_logs(remediation_id, \*, project_id) -> RemediationListResolvedLogsResponse +- client.projects.remediations.pause(remediation_id, \*, project_id) -> RemediationPauseResponse +- client.projects.remediations.publish(remediation_id, \*, project_id) -> RemediationPublishResponse +- client.projects.remediations.unpause(remediation_id, \*, project_id) -> RemediationUnpauseResponse + # Tlm Types: diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py index 2c0595d2..178855ac 100644 --- a/src/codex/resources/projects/__init__.py +++ b/src/codex/resources/projects/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .evals import ( + EvalsResource, + AsyncEvalsResource, + EvalsResourceWithRawResponse, + AsyncEvalsResourceWithRawResponse, + EvalsResourceWithStreamingResponse, + AsyncEvalsResourceWithStreamingResponse, +) from .entries import ( EntriesResource, AsyncEntriesResource, @@ -24,6 +32,14 @@ ProjectsResourceWithStreamingResponse, AsyncProjectsResourceWithStreamingResponse, ) +from .query_logs import ( + QueryLogsResource, + AsyncQueryLogsResource, + QueryLogsResourceWithRawResponse, + AsyncQueryLogsResourceWithRawResponse, + QueryLogsResourceWithStreamingResponse, + AsyncQueryLogsResourceWithStreamingResponse, +) from .access_keys import ( AccessKeysResource, AsyncAccessKeysResource, @@ -32,6 +48,14 @@ AccessKeysResourceWithStreamingResponse, AsyncAccessKeysResourceWithStreamingResponse, ) +from .remediations import ( + RemediationsResource, + AsyncRemediationsResource, + RemediationsResourceWithRawResponse, + AsyncRemediationsResourceWithRawResponse, + RemediationsResourceWithStreamingResponse, + AsyncRemediationsResourceWithStreamingResponse, +) __all__ = [ "AccessKeysResource", @@ -52,6 +76,24 @@ "AsyncClustersResourceWithRawResponse", "ClustersResourceWithStreamingResponse", "AsyncClustersResourceWithStreamingResponse", + "EvalsResource", + "AsyncEvalsResource", + "EvalsResourceWithRawResponse", + "AsyncEvalsResourceWithRawResponse", + "EvalsResourceWithStreamingResponse", + "AsyncEvalsResourceWithStreamingResponse", + "QueryLogsResource", + "AsyncQueryLogsResource", + "QueryLogsResourceWithRawResponse", + "AsyncQueryLogsResourceWithRawResponse", + "QueryLogsResourceWithStreamingResponse", + "AsyncQueryLogsResourceWithStreamingResponse", + "RemediationsResource", + "AsyncRemediationsResource", + "RemediationsResourceWithRawResponse", + "AsyncRemediationsResourceWithRawResponse", + "RemediationsResourceWithStreamingResponse", + "AsyncRemediationsResourceWithStreamingResponse", "ProjectsResource", "AsyncProjectsResource", "ProjectsResourceWithRawResponse", diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py new file mode 100644 index 00000000..1a6a6876 --- /dev/null +++ b/src/codex/resources/projects/evals.py @@ -0,0 +1,803 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, overload + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._utils import required_args, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.projects import eval_create_params, eval_update_params +from ...types.project_return_schema import ProjectReturnSchema +from ...types.projects.eval_list_response import EvalListResponse + +__all__ = ["EvalsResource", "AsyncEvalsResource"] + + +class EvalsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EvalsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return EvalsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EvalsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return EvalsResourceWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + criteria: str, + eval_key: str, + name: str, + context_identifier: Optional[str] | NotGiven = NOT_GIVEN, + enabled: bool | NotGiven = NOT_GIVEN, + is_default: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + query_identifier: Optional[str] | NotGiven = NOT_GIVEN, + response_identifier: Optional[str] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Create a new custom eval for a project. + + Args: + criteria: The evaluation criteria text that describes what aspect is being evaluated and + how + + eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + + name: Display name/label for the evaluation metric + + context_identifier: The exact string used in your evaluation criteria to reference the retrieved + context. + + enabled: Allows the evaluation to be disabled without removing it + + is_default: Whether the eval is a default, built-in eval or a custom eval + + priority: Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + + query_identifier: The exact string used in your evaluation criteria to reference the user's query. + + response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM + response. + + should_escalate: If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + + threshold: Threshold value that determines if the evaluation fails + + threshold_direction: Whether the evaluation fails when score is above or below the threshold + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/evals", + body=maybe_transform( + { + "criteria": criteria, + "eval_key": eval_key, + "name": name, + "context_identifier": context_identifier, + "enabled": enabled, + "is_default": is_default, + "priority": priority, + "query_identifier": query_identifier, + "response_identifier": response_identifier, + "should_escalate": should_escalate, + "threshold": threshold, + "threshold_direction": threshold_direction, + }, + eval_create_params.EvalCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + @overload + def update( + self, + path_eval_key: str, + *, + project_id: str, + criteria: str, + body_eval_key: str, + name: str, + context_identifier: Optional[str] | NotGiven = NOT_GIVEN, + enabled: bool | NotGiven = NOT_GIVEN, + is_default: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + query_identifier: Optional[str] | NotGiven = NOT_GIVEN, + response_identifier: Optional[str] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Update an existing eval for a project. + + Args: + criteria: The evaluation criteria text that describes what aspect is being evaluated and + how + + body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + + name: Display name/label for the evaluation metric + + context_identifier: The exact string used in your evaluation criteria to reference the retrieved + context. + + enabled: Allows the evaluation to be disabled without removing it + + is_default: Whether the eval is a default, built-in eval or a custom eval + + priority: Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + + query_identifier: The exact string used in your evaluation criteria to reference the user's query. + + response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM + response. + + should_escalate: If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + + threshold: Threshold value that determines if the evaluation fails + + threshold_direction: Whether the evaluation fails when score is above or below the threshold + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def update( + self, + path_eval_key: str, + *, + project_id: str, + body_eval_key: str, + enabled: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Update an existing eval for a project. + + Args: + body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + + enabled: Allows the evaluation to be disabled without removing it + + priority: Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + + should_escalate: If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + + threshold: Threshold value that determines if the evaluation fails + + threshold_direction: Whether the evaluation fails when score is above or below the threshold + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["project_id", "criteria", "body_eval_key", "name"], ["project_id", "body_eval_key"]) + def update( + self, + path_eval_key: str, + *, + project_id: str, + criteria: str | NotGiven = NOT_GIVEN, + body_eval_key: str, + name: str | NotGiven = NOT_GIVEN, + context_identifier: Optional[str] | NotGiven = NOT_GIVEN, + enabled: bool | NotGiven = NOT_GIVEN, + is_default: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + query_identifier: Optional[str] | NotGiven = NOT_GIVEN, + response_identifier: Optional[str] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not path_eval_key: + raise ValueError(f"Expected a non-empty value for `path_eval_key` but received {path_eval_key!r}") + return self._put( + f"/api/projects/{project_id}/evals/{path_eval_key}", + body=maybe_transform( + { + "criteria": criteria, + "body_eval_key": body_eval_key, + "name": name, + "context_identifier": context_identifier, + "enabled": enabled, + "is_default": is_default, + "priority": priority, + "query_identifier": query_identifier, + "response_identifier": response_identifier, + "should_escalate": should_escalate, + "threshold": threshold, + "threshold_direction": threshold_direction, + }, + eval_update_params.EvalUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + def list( + self, + project_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, + ) -> EvalListResponse: + """ + Get the evaluations config for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + f"/api/projects/{project_id}/evals", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EvalListResponse, + ) + + def delete( + self, + eval_key: str, + *, + project_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, + ) -> ProjectReturnSchema: + """ + Remove a custom eval for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not eval_key: + raise ValueError(f"Expected a non-empty value for `eval_key` but received {eval_key!r}") + return self._delete( + f"/api/projects/{project_id}/evals/{eval_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + +class AsyncEvalsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEvalsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncEvalsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEvalsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncEvalsResourceWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + criteria: str, + eval_key: str, + name: str, + context_identifier: Optional[str] | NotGiven = NOT_GIVEN, + enabled: bool | NotGiven = NOT_GIVEN, + is_default: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + query_identifier: Optional[str] | NotGiven = NOT_GIVEN, + response_identifier: Optional[str] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Create a new custom eval for a project. + + Args: + criteria: The evaluation criteria text that describes what aspect is being evaluated and + how + + eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + + name: Display name/label for the evaluation metric + + context_identifier: The exact string used in your evaluation criteria to reference the retrieved + context. + + enabled: Allows the evaluation to be disabled without removing it + + is_default: Whether the eval is a default, built-in eval or a custom eval + + priority: Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + + query_identifier: The exact string used in your evaluation criteria to reference the user's query. + + response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM + response. + + should_escalate: If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + + threshold: Threshold value that determines if the evaluation fails + + threshold_direction: Whether the evaluation fails when score is above or below the threshold + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/evals", + body=await async_maybe_transform( + { + "criteria": criteria, + "eval_key": eval_key, + "name": name, + "context_identifier": context_identifier, + "enabled": enabled, + "is_default": is_default, + "priority": priority, + "query_identifier": query_identifier, + "response_identifier": response_identifier, + "should_escalate": should_escalate, + "threshold": threshold, + "threshold_direction": threshold_direction, + }, + eval_create_params.EvalCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + @overload + async def update( + self, + path_eval_key: str, + *, + project_id: str, + criteria: str, + body_eval_key: str, + name: str, + context_identifier: Optional[str] | NotGiven = NOT_GIVEN, + enabled: bool | NotGiven = NOT_GIVEN, + is_default: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + query_identifier: Optional[str] | NotGiven = NOT_GIVEN, + response_identifier: Optional[str] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Update an existing eval for a project. + + Args: + criteria: The evaluation criteria text that describes what aspect is being evaluated and + how + + body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + + name: Display name/label for the evaluation metric + + context_identifier: The exact string used in your evaluation criteria to reference the retrieved + context. + + enabled: Allows the evaluation to be disabled without removing it + + is_default: Whether the eval is a default, built-in eval or a custom eval + + priority: Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + + query_identifier: The exact string used in your evaluation criteria to reference the user's query. + + response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM + response. + + should_escalate: If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + + threshold: Threshold value that determines if the evaluation fails + + threshold_direction: Whether the evaluation fails when score is above or below the threshold + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def update( + self, + path_eval_key: str, + *, + project_id: str, + body_eval_key: str, + enabled: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + """ + Update an existing eval for a project. + + Args: + body_eval_key: Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + + enabled: Allows the evaluation to be disabled without removing it + + priority: Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + + should_escalate: If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + + threshold: Threshold value that determines if the evaluation fails + + threshold_direction: Whether the evaluation fails when score is above or below the threshold + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["project_id", "criteria", "body_eval_key", "name"], ["project_id", "body_eval_key"]) + async def update( + self, + path_eval_key: str, + *, + project_id: str, + criteria: str | NotGiven = NOT_GIVEN, + body_eval_key: str, + name: str | NotGiven = NOT_GIVEN, + context_identifier: Optional[str] | NotGiven = NOT_GIVEN, + enabled: bool | NotGiven = NOT_GIVEN, + is_default: bool | NotGiven = NOT_GIVEN, + priority: Optional[int] | NotGiven = NOT_GIVEN, + query_identifier: Optional[str] | NotGiven = NOT_GIVEN, + response_identifier: Optional[str] | NotGiven = NOT_GIVEN, + should_escalate: bool | NotGiven = NOT_GIVEN, + threshold: float | NotGiven = NOT_GIVEN, + threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + # 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, + ) -> ProjectReturnSchema: + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not path_eval_key: + raise ValueError(f"Expected a non-empty value for `path_eval_key` but received {path_eval_key!r}") + return await self._put( + f"/api/projects/{project_id}/evals/{path_eval_key}", + body=await async_maybe_transform( + { + "criteria": criteria, + "body_eval_key": body_eval_key, + "name": name, + "context_identifier": context_identifier, + "enabled": enabled, + "is_default": is_default, + "priority": priority, + "query_identifier": query_identifier, + "response_identifier": response_identifier, + "should_escalate": should_escalate, + "threshold": threshold, + "threshold_direction": threshold_direction, + }, + eval_update_params.EvalUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + async def list( + self, + project_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, + ) -> EvalListResponse: + """ + Get the evaluations config for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + f"/api/projects/{project_id}/evals", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EvalListResponse, + ) + + async def delete( + self, + eval_key: str, + *, + project_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, + ) -> ProjectReturnSchema: + """ + Remove a custom eval for a project. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not eval_key: + raise ValueError(f"Expected a non-empty value for `eval_key` but received {eval_key!r}") + return await self._delete( + f"/api/projects/{project_id}/evals/{eval_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectReturnSchema, + ) + + +class EvalsResourceWithRawResponse: + def __init__(self, evals: EvalsResource) -> None: + self._evals = evals + + self.create = to_raw_response_wrapper( + evals.create, + ) + self.update = to_raw_response_wrapper( + evals.update, + ) + self.list = to_raw_response_wrapper( + evals.list, + ) + self.delete = to_raw_response_wrapper( + evals.delete, + ) + + +class AsyncEvalsResourceWithRawResponse: + def __init__(self, evals: AsyncEvalsResource) -> None: + self._evals = evals + + self.create = async_to_raw_response_wrapper( + evals.create, + ) + self.update = async_to_raw_response_wrapper( + evals.update, + ) + self.list = async_to_raw_response_wrapper( + evals.list, + ) + self.delete = async_to_raw_response_wrapper( + evals.delete, + ) + + +class EvalsResourceWithStreamingResponse: + def __init__(self, evals: EvalsResource) -> None: + self._evals = evals + + self.create = to_streamed_response_wrapper( + evals.create, + ) + self.update = to_streamed_response_wrapper( + evals.update, + ) + self.list = to_streamed_response_wrapper( + evals.list, + ) + self.delete = to_streamed_response_wrapper( + evals.delete, + ) + + +class AsyncEvalsResourceWithStreamingResponse: + def __init__(self, evals: AsyncEvalsResource) -> None: + self._evals = evals + + self.create = async_to_streamed_response_wrapper( + evals.create, + ) + self.update = async_to_streamed_response_wrapper( + evals.update, + ) + self.list = async_to_streamed_response_wrapper( + evals.list, + ) + self.delete = async_to_streamed_response_wrapper( + evals.delete, + ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 11b95d4d..22b5caff 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -8,11 +8,20 @@ import httpx +from .evals import ( + EvalsResource, + AsyncEvalsResource, + EvalsResourceWithRawResponse, + AsyncEvalsResourceWithRawResponse, + EvalsResourceWithStreamingResponse, + AsyncEvalsResourceWithStreamingResponse, +) from ...types import ( project_list_params, project_create_params, project_update_params, project_validate_params, + project_invite_sme_params, project_increment_queries_params, project_retrieve_analytics_params, ) @@ -35,6 +44,14 @@ AsyncClustersResourceWithStreamingResponse, ) from ..._compat import cached_property +from .query_logs import ( + QueryLogsResource, + AsyncQueryLogsResource, + QueryLogsResourceWithRawResponse, + AsyncQueryLogsResourceWithRawResponse, + QueryLogsResourceWithStreamingResponse, + AsyncQueryLogsResourceWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( to_raw_response_wrapper, @@ -50,11 +67,20 @@ AccessKeysResourceWithStreamingResponse, AsyncAccessKeysResourceWithStreamingResponse, ) +from .remediations import ( + RemediationsResource, + AsyncRemediationsResource, + RemediationsResourceWithRawResponse, + AsyncRemediationsResourceWithRawResponse, + RemediationsResourceWithStreamingResponse, + AsyncRemediationsResourceWithStreamingResponse, +) from ..._base_client import make_request_options from ...types.project_list_response import ProjectListResponse from ...types.project_return_schema import ProjectReturnSchema from ...types.project_retrieve_response import ProjectRetrieveResponse from ...types.project_validate_response import ProjectValidateResponse +from ...types.project_invite_sme_response import ProjectInviteSmeResponse from ...types.project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -73,6 +99,18 @@ def entries(self) -> EntriesResource: def clusters(self) -> ClustersResource: return ClustersResource(self._client) + @cached_property + def evals(self) -> EvalsResource: + return EvalsResource(self._client) + + @cached_property + def query_logs(self) -> QueryLogsResource: + return QueryLogsResource(self._client) + + @cached_property + def remediations(self) -> RemediationsResource: + return RemediationsResource(self._client) + @cached_property def with_raw_response(self) -> ProjectsResourceWithRawResponse: """ @@ -374,6 +412,52 @@ def increment_queries( cast_to=object, ) + def invite_sme( + self, + project_id: str, + *, + email: str, + page_type: Literal["query_log", "remediation"], + url_query_string: 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, + ) -> ProjectInviteSmeResponse: + """ + Invite a subject matter expert to view a specific query log or remediation. + + Returns: SMERemediationNotificationResponse with status and notification details + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/notifications", + body=maybe_transform( + { + "email": email, + "page_type": page_type, + "url_query_string": url_query_string, + }, + project_invite_sme_params.ProjectInviteSmeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectInviteSmeResponse, + ) + def retrieve_analytics( self, project_id: str, @@ -623,6 +707,18 @@ def entries(self) -> AsyncEntriesResource: def clusters(self) -> AsyncClustersResource: return AsyncClustersResource(self._client) + @cached_property + def evals(self) -> AsyncEvalsResource: + return AsyncEvalsResource(self._client) + + @cached_property + def query_logs(self) -> AsyncQueryLogsResource: + return AsyncQueryLogsResource(self._client) + + @cached_property + def remediations(self) -> AsyncRemediationsResource: + return AsyncRemediationsResource(self._client) + @cached_property def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse: """ @@ -926,6 +1022,52 @@ async def increment_queries( cast_to=object, ) + async def invite_sme( + self, + project_id: str, + *, + email: str, + page_type: Literal["query_log", "remediation"], + url_query_string: 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, + ) -> ProjectInviteSmeResponse: + """ + Invite a subject matter expert to view a specific query log or remediation. + + Returns: SMERemediationNotificationResponse with status and notification details + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/notifications", + body=await async_maybe_transform( + { + "email": email, + "page_type": page_type, + "url_query_string": url_query_string, + }, + project_invite_sme_params.ProjectInviteSmeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectInviteSmeResponse, + ) + async def retrieve_analytics( self, project_id: str, @@ -1189,6 +1331,9 @@ def __init__(self, projects: ProjectsResource) -> None: projects.increment_queries # pyright: ignore[reportDeprecated], ) ) + self.invite_sme = to_raw_response_wrapper( + projects.invite_sme, + ) self.retrieve_analytics = to_raw_response_wrapper( projects.retrieve_analytics, ) @@ -1208,6 +1353,18 @@ def entries(self) -> EntriesResourceWithRawResponse: def clusters(self) -> ClustersResourceWithRawResponse: return ClustersResourceWithRawResponse(self._projects.clusters) + @cached_property + def evals(self) -> EvalsResourceWithRawResponse: + return EvalsResourceWithRawResponse(self._projects.evals) + + @cached_property + def query_logs(self) -> QueryLogsResourceWithRawResponse: + return QueryLogsResourceWithRawResponse(self._projects.query_logs) + + @cached_property + def remediations(self) -> RemediationsResourceWithRawResponse: + return RemediationsResourceWithRawResponse(self._projects.remediations) + class AsyncProjectsResourceWithRawResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -1236,6 +1393,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: projects.increment_queries # pyright: ignore[reportDeprecated], ) ) + self.invite_sme = async_to_raw_response_wrapper( + projects.invite_sme, + ) self.retrieve_analytics = async_to_raw_response_wrapper( projects.retrieve_analytics, ) @@ -1255,6 +1415,18 @@ def entries(self) -> AsyncEntriesResourceWithRawResponse: def clusters(self) -> AsyncClustersResourceWithRawResponse: return AsyncClustersResourceWithRawResponse(self._projects.clusters) + @cached_property + def evals(self) -> AsyncEvalsResourceWithRawResponse: + return AsyncEvalsResourceWithRawResponse(self._projects.evals) + + @cached_property + def query_logs(self) -> AsyncQueryLogsResourceWithRawResponse: + return AsyncQueryLogsResourceWithRawResponse(self._projects.query_logs) + + @cached_property + def remediations(self) -> AsyncRemediationsResourceWithRawResponse: + return AsyncRemediationsResourceWithRawResponse(self._projects.remediations) + class ProjectsResourceWithStreamingResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -1283,6 +1455,9 @@ def __init__(self, projects: ProjectsResource) -> None: projects.increment_queries # pyright: ignore[reportDeprecated], ) ) + self.invite_sme = to_streamed_response_wrapper( + projects.invite_sme, + ) self.retrieve_analytics = to_streamed_response_wrapper( projects.retrieve_analytics, ) @@ -1302,6 +1477,18 @@ def entries(self) -> EntriesResourceWithStreamingResponse: def clusters(self) -> ClustersResourceWithStreamingResponse: return ClustersResourceWithStreamingResponse(self._projects.clusters) + @cached_property + def evals(self) -> EvalsResourceWithStreamingResponse: + return EvalsResourceWithStreamingResponse(self._projects.evals) + + @cached_property + def query_logs(self) -> QueryLogsResourceWithStreamingResponse: + return QueryLogsResourceWithStreamingResponse(self._projects.query_logs) + + @cached_property + def remediations(self) -> RemediationsResourceWithStreamingResponse: + return RemediationsResourceWithStreamingResponse(self._projects.remediations) + class AsyncProjectsResourceWithStreamingResponse: def __init__(self, projects: AsyncProjectsResource) -> None: @@ -1330,6 +1517,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: projects.increment_queries # pyright: ignore[reportDeprecated], ) ) + self.invite_sme = async_to_streamed_response_wrapper( + projects.invite_sme, + ) self.retrieve_analytics = async_to_streamed_response_wrapper( projects.retrieve_analytics, ) @@ -1348,3 +1538,15 @@ def entries(self) -> AsyncEntriesResourceWithStreamingResponse: @cached_property def clusters(self) -> AsyncClustersResourceWithStreamingResponse: return AsyncClustersResourceWithStreamingResponse(self._projects.clusters) + + @cached_property + def evals(self) -> AsyncEvalsResourceWithStreamingResponse: + return AsyncEvalsResourceWithStreamingResponse(self._projects.evals) + + @cached_property + def query_logs(self) -> AsyncQueryLogsResourceWithStreamingResponse: + return AsyncQueryLogsResourceWithStreamingResponse(self._projects.query_logs) + + @cached_property + def remediations(self) -> AsyncRemediationsResourceWithStreamingResponse: + return AsyncRemediationsResourceWithStreamingResponse(self._projects.remediations) diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py new file mode 100644 index 00000000..32ec7390 --- /dev/null +++ b/src/codex/resources/projects/query_logs.py @@ -0,0 +1,741 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +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.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params +from ...types.projects.query_log_list_response import QueryLogListResponse +from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse +from ...types.projects.query_log_list_groups_response import QueryLogListGroupsResponse +from ...types.projects.query_log_list_by_group_response import QueryLogListByGroupResponse +from ...types.projects.query_log_start_remediation_response import QueryLogStartRemediationResponse + +__all__ = ["QueryLogsResource", "AsyncQueryLogsResource"] + + +class QueryLogsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> QueryLogsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return QueryLogsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> QueryLogsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return QueryLogsResourceWithStreamingResponse(self) + + def retrieve( + self, + query_log_id: str, + *, + project_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, + ) -> QueryLogRetrieveResponse: + """ + Get Query Log Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return self._get( + f"/api/projects/{project_id}/query_logs/{query_log_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogRetrieveResponse, + ) + + def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + # 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, + ) -> QueryLogListResponse: + """ + List query logs by project ID. + + Args: + created_at_end: Filter logs created at or before this timestamp + + created_at_start: Filter logs created at or after this timestamp + + custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + + was_cache_hit: Filter by cache hit status + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + f"/api/projects/{project_id}/query_logs/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "custom_metadata": custom_metadata, + "limit": limit, + "offset": offset, + "order": order, + "primary_eval_issue": primary_eval_issue, + "sort": sort, + "was_cache_hit": was_cache_hit, + }, + query_log_list_params.QueryLogListParams, + ), + ), + cast_to=QueryLogListResponse, + ) + + def list_by_group( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + | NotGiven = NOT_GIVEN, + remediation_ids: List[str] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + # 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, + ) -> QueryLogListByGroupResponse: + """ + List query log group by remediation ID. + + Args: + created_at_end: Filter logs created at or before this timestamp + + created_at_start: Filter logs created at or after this timestamp + + custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + + remediation_ids: List of groups to list child logs for + + was_cache_hit: Filter by cache hit status + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + f"/api/projects/{project_id}/query_logs/logs_by_group", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "custom_metadata": custom_metadata, + "limit": limit, + "offset": offset, + "order": order, + "primary_eval_issue": primary_eval_issue, + "remediation_ids": remediation_ids, + "sort": sort, + "was_cache_hit": was_cache_hit, + }, + query_log_list_by_group_params.QueryLogListByGroupParams, + ), + ), + cast_to=QueryLogListByGroupResponse, + ) + + def list_groups( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] + | NotGiven = NOT_GIVEN, + was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + # 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, + ) -> QueryLogListGroupsResponse: + """ + List query log groups by project ID. + + Args: + created_at_end: Filter logs created at or before this timestamp + + created_at_start: Filter logs created at or after this timestamp + + custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + + was_cache_hit: Filter by cache hit status + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + f"/api/projects/{project_id}/query_logs/groups", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "custom_metadata": custom_metadata, + "limit": limit, + "offset": offset, + "order": order, + "primary_eval_issue": primary_eval_issue, + "sort": sort, + "was_cache_hit": was_cache_hit, + }, + query_log_list_groups_params.QueryLogListGroupsParams, + ), + ), + cast_to=QueryLogListGroupsResponse, + ) + + def start_remediation( + self, + query_log_id: str, + *, + project_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, + ) -> QueryLogStartRemediationResponse: + """ + Start Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return self._post( + f"/api/projects/{project_id}/query_logs/{query_log_id}/start_remediation", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogStartRemediationResponse, + ) + + +class AsyncQueryLogsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncQueryLogsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncQueryLogsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncQueryLogsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncQueryLogsResourceWithStreamingResponse(self) + + async def retrieve( + self, + query_log_id: str, + *, + project_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, + ) -> QueryLogRetrieveResponse: + """ + Get Query Log Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return await self._get( + f"/api/projects/{project_id}/query_logs/{query_log_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogRetrieveResponse, + ) + + async def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + # 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, + ) -> QueryLogListResponse: + """ + List query logs by project ID. + + Args: + created_at_end: Filter logs created at or before this timestamp + + created_at_start: Filter logs created at or after this timestamp + + custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + + was_cache_hit: Filter by cache hit status + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + f"/api/projects/{project_id}/query_logs/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "custom_metadata": custom_metadata, + "limit": limit, + "offset": offset, + "order": order, + "primary_eval_issue": primary_eval_issue, + "sort": sort, + "was_cache_hit": was_cache_hit, + }, + query_log_list_params.QueryLogListParams, + ), + ), + cast_to=QueryLogListResponse, + ) + + async def list_by_group( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + | NotGiven = NOT_GIVEN, + remediation_ids: List[str] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + # 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, + ) -> QueryLogListByGroupResponse: + """ + List query log group by remediation ID. + + Args: + created_at_end: Filter logs created at or before this timestamp + + created_at_start: Filter logs created at or after this timestamp + + custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + + remediation_ids: List of groups to list child logs for + + was_cache_hit: Filter by cache hit status + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + f"/api/projects/{project_id}/query_logs/logs_by_group", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "custom_metadata": custom_metadata, + "limit": limit, + "offset": offset, + "order": order, + "primary_eval_issue": primary_eval_issue, + "remediation_ids": remediation_ids, + "sort": sort, + "was_cache_hit": was_cache_hit, + }, + query_log_list_by_group_params.QueryLogListByGroupParams, + ), + ), + cast_to=QueryLogListByGroupResponse, + ) + + async def list_groups( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] + | NotGiven = NOT_GIVEN, + was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + # 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, + ) -> QueryLogListGroupsResponse: + """ + List query log groups by project ID. + + Args: + created_at_end: Filter logs created at or before this timestamp + + created_at_start: Filter logs created at or after this timestamp + + custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + + was_cache_hit: Filter by cache hit status + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + f"/api/projects/{project_id}/query_logs/groups", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "custom_metadata": custom_metadata, + "limit": limit, + "offset": offset, + "order": order, + "primary_eval_issue": primary_eval_issue, + "sort": sort, + "was_cache_hit": was_cache_hit, + }, + query_log_list_groups_params.QueryLogListGroupsParams, + ), + ), + cast_to=QueryLogListGroupsResponse, + ) + + async def start_remediation( + self, + query_log_id: str, + *, + project_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, + ) -> QueryLogStartRemediationResponse: + """ + Start Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return await self._post( + f"/api/projects/{project_id}/query_logs/{query_log_id}/start_remediation", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogStartRemediationResponse, + ) + + +class QueryLogsResourceWithRawResponse: + def __init__(self, query_logs: QueryLogsResource) -> None: + self._query_logs = query_logs + + self.retrieve = to_raw_response_wrapper( + query_logs.retrieve, + ) + self.list = to_raw_response_wrapper( + query_logs.list, + ) + self.list_by_group = to_raw_response_wrapper( + query_logs.list_by_group, + ) + self.list_groups = to_raw_response_wrapper( + query_logs.list_groups, + ) + self.start_remediation = to_raw_response_wrapper( + query_logs.start_remediation, + ) + + +class AsyncQueryLogsResourceWithRawResponse: + def __init__(self, query_logs: AsyncQueryLogsResource) -> None: + self._query_logs = query_logs + + self.retrieve = async_to_raw_response_wrapper( + query_logs.retrieve, + ) + self.list = async_to_raw_response_wrapper( + query_logs.list, + ) + self.list_by_group = async_to_raw_response_wrapper( + query_logs.list_by_group, + ) + self.list_groups = async_to_raw_response_wrapper( + query_logs.list_groups, + ) + self.start_remediation = async_to_raw_response_wrapper( + query_logs.start_remediation, + ) + + +class QueryLogsResourceWithStreamingResponse: + def __init__(self, query_logs: QueryLogsResource) -> None: + self._query_logs = query_logs + + self.retrieve = to_streamed_response_wrapper( + query_logs.retrieve, + ) + self.list = to_streamed_response_wrapper( + query_logs.list, + ) + self.list_by_group = to_streamed_response_wrapper( + query_logs.list_by_group, + ) + self.list_groups = to_streamed_response_wrapper( + query_logs.list_groups, + ) + self.start_remediation = to_streamed_response_wrapper( + query_logs.start_remediation, + ) + + +class AsyncQueryLogsResourceWithStreamingResponse: + def __init__(self, query_logs: AsyncQueryLogsResource) -> None: + self._query_logs = query_logs + + self.retrieve = async_to_streamed_response_wrapper( + query_logs.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + query_logs.list, + ) + self.list_by_group = async_to_streamed_response_wrapper( + query_logs.list_by_group, + ) + self.list_groups = async_to_streamed_response_wrapper( + query_logs.list_groups, + ) + self.start_remediation = async_to_streamed_response_wrapper( + query_logs.start_remediation, + ) diff --git a/src/codex/resources/projects/remediations.py b/src/codex/resources/projects/remediations.py new file mode 100644 index 00000000..65015a14 --- /dev/null +++ b/src/codex/resources/projects/remediations.py @@ -0,0 +1,1135 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +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.projects import ( + remediation_list_params, + remediation_create_params, + remediation_edit_answer_params, + remediation_edit_draft_answer_params, +) +from ...types.projects.remediation_list_response import RemediationListResponse +from ...types.projects.remediation_pause_response import RemediationPauseResponse +from ...types.projects.remediation_create_response import RemediationCreateResponse +from ...types.projects.remediation_publish_response import RemediationPublishResponse +from ...types.projects.remediation_unpause_response import RemediationUnpauseResponse +from ...types.projects.remediation_retrieve_response import RemediationRetrieveResponse +from ...types.projects.remediation_edit_answer_response import RemediationEditAnswerResponse +from ...types.projects.remediation_edit_draft_answer_response import RemediationEditDraftAnswerResponse +from ...types.projects.remediation_list_resolved_logs_response import RemediationListResolvedLogsResponse +from ...types.projects.remediation_get_resolved_logs_count_response import RemediationGetResolvedLogsCountResponse + +__all__ = ["RemediationsResource", "AsyncRemediationsResource"] + + +class RemediationsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RemediationsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return RemediationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RemediationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return RemediationsResourceWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + question: str, + answer: Optional[str] | NotGiven = NOT_GIVEN, + draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> RemediationCreateResponse: + """ + Create Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/remediations/", + body=maybe_transform( + { + "question": question, + "answer": answer, + "draft_answer": draft_answer, + }, + remediation_create_params.RemediationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationCreateResponse, + ) + + def retrieve( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationRetrieveResponse: + """ + Get Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._get( + f"/api/projects/{project_id}/remediations/{remediation_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationRetrieveResponse, + ) + + def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + last_edited_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + last_edited_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + last_edited_by: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | NotGiven = NOT_GIVEN, + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | NotGiven = NOT_GIVEN, + # 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, + ) -> RemediationListResponse: + """ + List remediations by project ID. + + Args: + created_at_end: Filter remediations created at or before this timestamp + + created_at_start: Filter remediations created at or after this timestamp + + last_edited_at_end: Filter remediations last edited at or before this timestamp + + last_edited_at_start: Filter remediations last edited at or after this timestamp + + last_edited_by: Filter by last edited by user ID + + status: Filter remediations that have ANY of these statuses (OR operation) + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + f"/api/projects/{project_id}/remediations/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "last_edited_at_end": last_edited_at_end, + "last_edited_at_start": last_edited_at_start, + "last_edited_by": last_edited_by, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "status": status, + }, + remediation_list_params.RemediationListParams, + ), + ), + cast_to=RemediationListResponse, + ) + + def delete( + self, + remediation_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/api/projects/{project_id}/remediations/{remediation_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def edit_answer( + self, + remediation_id: str, + *, + project_id: str, + answer: 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, + ) -> RemediationEditAnswerResponse: + """ + Edit Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/edit_answer", + body=maybe_transform({"answer": answer}, remediation_edit_answer_params.RemediationEditAnswerParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationEditAnswerResponse, + ) + + def edit_draft_answer( + self, + remediation_id: str, + *, + project_id: str, + draft_answer: 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, + ) -> RemediationEditDraftAnswerResponse: + """ + Edit Draft Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/edit_draft_answer", + body=maybe_transform( + {"draft_answer": draft_answer}, remediation_edit_draft_answer_params.RemediationEditDraftAnswerParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationEditDraftAnswerResponse, + ) + + def get_resolved_logs_count( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationGetResolvedLogsCountResponse: + """ + Get Remediation With Resolved Logs Count Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._get( + f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs_count", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationGetResolvedLogsCountResponse, + ) + + def list_resolved_logs( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationListResolvedLogsResponse: + """ + List resolved logs by remediation 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._get( + f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationListResolvedLogsResponse, + ) + + def pause( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationPauseResponse: + """ + Pause Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationPauseResponse, + ) + + def publish( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationPublishResponse: + """ + Publish Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/publish", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationPublishResponse, + ) + + def unpause( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationUnpauseResponse: + """ + Unpause Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationUnpauseResponse, + ) + + +class AsyncRemediationsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRemediationsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncRemediationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRemediationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncRemediationsResourceWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + question: str, + answer: Optional[str] | NotGiven = NOT_GIVEN, + draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + # 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, + ) -> RemediationCreateResponse: + """ + Create Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/remediations/", + body=await async_maybe_transform( + { + "question": question, + "answer": answer, + "draft_answer": draft_answer, + }, + remediation_create_params.RemediationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationCreateResponse, + ) + + async def retrieve( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationRetrieveResponse: + """ + Get Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._get( + f"/api/projects/{project_id}/remediations/{remediation_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationRetrieveResponse, + ) + + async def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + last_edited_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + last_edited_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + last_edited_by: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | NotGiven = NOT_GIVEN, + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | NotGiven = NOT_GIVEN, + # 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, + ) -> RemediationListResponse: + """ + List remediations by project ID. + + Args: + created_at_end: Filter remediations created at or before this timestamp + + created_at_start: Filter remediations created at or after this timestamp + + last_edited_at_end: Filter remediations last edited at or before this timestamp + + last_edited_at_start: Filter remediations last edited at or after this timestamp + + last_edited_by: Filter by last edited by user ID + + status: Filter remediations that have ANY of these statuses (OR operation) + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + f"/api/projects/{project_id}/remediations/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "last_edited_at_end": last_edited_at_end, + "last_edited_at_start": last_edited_at_start, + "last_edited_by": last_edited_by, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "status": status, + }, + remediation_list_params.RemediationListParams, + ), + ), + cast_to=RemediationListResponse, + ) + + async def delete( + self, + remediation_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> None: + """ + Delete Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/api/projects/{project_id}/remediations/{remediation_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def edit_answer( + self, + remediation_id: str, + *, + project_id: str, + answer: 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, + ) -> RemediationEditAnswerResponse: + """ + Edit Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/edit_answer", + body=await async_maybe_transform( + {"answer": answer}, remediation_edit_answer_params.RemediationEditAnswerParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationEditAnswerResponse, + ) + + async def edit_draft_answer( + self, + remediation_id: str, + *, + project_id: str, + draft_answer: 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, + ) -> RemediationEditDraftAnswerResponse: + """ + Edit Draft Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/edit_draft_answer", + body=await async_maybe_transform( + {"draft_answer": draft_answer}, remediation_edit_draft_answer_params.RemediationEditDraftAnswerParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationEditDraftAnswerResponse, + ) + + async def get_resolved_logs_count( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationGetResolvedLogsCountResponse: + """ + Get Remediation With Resolved Logs Count Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._get( + f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs_count", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationGetResolvedLogsCountResponse, + ) + + async def list_resolved_logs( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationListResolvedLogsResponse: + """ + List resolved logs by remediation 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._get( + f"/api/projects/{project_id}/remediations/{remediation_id}/resolved_logs", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationListResolvedLogsResponse, + ) + + async def pause( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationPauseResponse: + """ + Pause Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationPauseResponse, + ) + + async def publish( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationPublishResponse: + """ + Publish Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/publish", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationPublishResponse, + ) + + async def unpause( + self, + remediation_id: str, + *, + project_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, + ) -> RemediationUnpauseResponse: + """ + Unpause Remediation Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not remediation_id: + raise ValueError(f"Expected a non-empty value for `remediation_id` but received {remediation_id!r}") + return await self._patch( + f"/api/projects/{project_id}/remediations/{remediation_id}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RemediationUnpauseResponse, + ) + + +class RemediationsResourceWithRawResponse: + def __init__(self, remediations: RemediationsResource) -> None: + self._remediations = remediations + + self.create = to_raw_response_wrapper( + remediations.create, + ) + self.retrieve = to_raw_response_wrapper( + remediations.retrieve, + ) + self.list = to_raw_response_wrapper( + remediations.list, + ) + self.delete = to_raw_response_wrapper( + remediations.delete, + ) + self.edit_answer = to_raw_response_wrapper( + remediations.edit_answer, + ) + self.edit_draft_answer = to_raw_response_wrapper( + remediations.edit_draft_answer, + ) + self.get_resolved_logs_count = to_raw_response_wrapper( + remediations.get_resolved_logs_count, + ) + self.list_resolved_logs = to_raw_response_wrapper( + remediations.list_resolved_logs, + ) + self.pause = to_raw_response_wrapper( + remediations.pause, + ) + self.publish = to_raw_response_wrapper( + remediations.publish, + ) + self.unpause = to_raw_response_wrapper( + remediations.unpause, + ) + + +class AsyncRemediationsResourceWithRawResponse: + def __init__(self, remediations: AsyncRemediationsResource) -> None: + self._remediations = remediations + + self.create = async_to_raw_response_wrapper( + remediations.create, + ) + self.retrieve = async_to_raw_response_wrapper( + remediations.retrieve, + ) + self.list = async_to_raw_response_wrapper( + remediations.list, + ) + self.delete = async_to_raw_response_wrapper( + remediations.delete, + ) + self.edit_answer = async_to_raw_response_wrapper( + remediations.edit_answer, + ) + self.edit_draft_answer = async_to_raw_response_wrapper( + remediations.edit_draft_answer, + ) + self.get_resolved_logs_count = async_to_raw_response_wrapper( + remediations.get_resolved_logs_count, + ) + self.list_resolved_logs = async_to_raw_response_wrapper( + remediations.list_resolved_logs, + ) + self.pause = async_to_raw_response_wrapper( + remediations.pause, + ) + self.publish = async_to_raw_response_wrapper( + remediations.publish, + ) + self.unpause = async_to_raw_response_wrapper( + remediations.unpause, + ) + + +class RemediationsResourceWithStreamingResponse: + def __init__(self, remediations: RemediationsResource) -> None: + self._remediations = remediations + + self.create = to_streamed_response_wrapper( + remediations.create, + ) + self.retrieve = to_streamed_response_wrapper( + remediations.retrieve, + ) + self.list = to_streamed_response_wrapper( + remediations.list, + ) + self.delete = to_streamed_response_wrapper( + remediations.delete, + ) + self.edit_answer = to_streamed_response_wrapper( + remediations.edit_answer, + ) + self.edit_draft_answer = to_streamed_response_wrapper( + remediations.edit_draft_answer, + ) + self.get_resolved_logs_count = to_streamed_response_wrapper( + remediations.get_resolved_logs_count, + ) + self.list_resolved_logs = to_streamed_response_wrapper( + remediations.list_resolved_logs, + ) + self.pause = to_streamed_response_wrapper( + remediations.pause, + ) + self.publish = to_streamed_response_wrapper( + remediations.publish, + ) + self.unpause = to_streamed_response_wrapper( + remediations.unpause, + ) + + +class AsyncRemediationsResourceWithStreamingResponse: + def __init__(self, remediations: AsyncRemediationsResource) -> None: + self._remediations = remediations + + self.create = async_to_streamed_response_wrapper( + remediations.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + remediations.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + remediations.list, + ) + self.delete = async_to_streamed_response_wrapper( + remediations.delete, + ) + self.edit_answer = async_to_streamed_response_wrapper( + remediations.edit_answer, + ) + self.edit_draft_answer = async_to_streamed_response_wrapper( + remediations.edit_draft_answer, + ) + self.get_resolved_logs_count = async_to_streamed_response_wrapper( + remediations.get_resolved_logs_count, + ) + self.list_resolved_logs = async_to_streamed_response_wrapper( + remediations.list_resolved_logs, + ) + self.pause = async_to_streamed_response_wrapper( + remediations.pause, + ) + self.publish = async_to_streamed_response_wrapper( + remediations.publish, + ) + self.unpause = async_to_streamed_response_wrapper( + remediations.unpause, + ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 8e0cc4a4..70713a3b 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -13,9 +13,11 @@ from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema from .project_update_params import ProjectUpdateParams as ProjectUpdateParams from .project_validate_params import ProjectValidateParams as ProjectValidateParams +from .project_invite_sme_params import ProjectInviteSmeParams as ProjectInviteSmeParams from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .project_validate_response import ProjectValidateResponse as ProjectValidateResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic +from .project_invite_sme_response import ProjectInviteSmeResponse as ProjectInviteSmeResponse from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams diff --git a/src/codex/types/project_invite_sme_params.py b/src/codex/types/project_invite_sme_params.py new file mode 100644 index 00000000..f2694632 --- /dev/null +++ b/src/codex/types/project_invite_sme_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ProjectInviteSmeParams"] + + +class ProjectInviteSmeParams(TypedDict, total=False): + email: Required[str] + + page_type: Required[Literal["query_log", "remediation"]] + + url_query_string: Required[str] diff --git a/src/codex/types/project_invite_sme_response.py b/src/codex/types/project_invite_sme_response.py new file mode 100644 index 00000000..8c871a57 --- /dev/null +++ b/src/codex/types/project_invite_sme_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["ProjectInviteSmeResponse"] + + +class ProjectInviteSmeResponse(BaseModel): + recipient_email: str + + status: str diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 2b69e570..8a26aa00 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -5,17 +5,48 @@ from .entry import Entry as Entry from .access_key_schema import AccessKeySchema as AccessKeySchema from .entry_query_params import EntryQueryParams as EntryQueryParams +from .eval_create_params import EvalCreateParams as EvalCreateParams +from .eval_list_response import EvalListResponse as EvalListResponse +from .eval_update_params import EvalUpdateParams as EvalUpdateParams from .cluster_list_params import ClusterListParams as ClusterListParams from .entry_create_params import EntryCreateParams as EntryCreateParams from .entry_update_params import EntryUpdateParams as EntryUpdateParams from .entry_query_response import EntryQueryResponse as EntryQueryResponse from .cluster_list_response import ClusterListResponse as ClusterListResponse +from .query_log_list_params import QueryLogListParams as QueryLogListParams from .entry_notify_sme_params import EntryNotifySmeParams as EntryNotifySmeParams +from .query_log_list_response import QueryLogListResponse as QueryLogListResponse +from .remediation_list_params import RemediationListParams as RemediationListParams from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams from .entry_notify_sme_response import EntryNotifySmeResponse as EntryNotifySmeResponse +from .remediation_create_params import RemediationCreateParams as RemediationCreateParams +from .remediation_list_response import RemediationListResponse as RemediationListResponse +from .remediation_pause_response import RemediationPauseResponse as RemediationPauseResponse +from .query_log_retrieve_response import QueryLogRetrieveResponse as QueryLogRetrieveResponse +from .remediation_create_response import RemediationCreateResponse as RemediationCreateResponse +from .query_log_list_groups_params import QueryLogListGroupsParams as QueryLogListGroupsParams +from .remediation_publish_response import RemediationPublishResponse as RemediationPublishResponse +from .remediation_unpause_response import RemediationUnpauseResponse as RemediationUnpauseResponse +from .remediation_retrieve_response import RemediationRetrieveResponse as RemediationRetrieveResponse from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse +from .query_log_list_by_group_params import QueryLogListByGroupParams as QueryLogListByGroupParams +from .query_log_list_groups_response import QueryLogListGroupsResponse as QueryLogListGroupsResponse +from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams +from .query_log_list_by_group_response import QueryLogListByGroupResponse as QueryLogListByGroupResponse +from .remediation_edit_answer_response import RemediationEditAnswerResponse as RemediationEditAnswerResponse +from .query_log_start_remediation_response import QueryLogStartRemediationResponse as QueryLogStartRemediationResponse +from .remediation_edit_draft_answer_params import RemediationEditDraftAnswerParams as RemediationEditDraftAnswerParams +from .remediation_edit_draft_answer_response import ( + RemediationEditDraftAnswerResponse as RemediationEditDraftAnswerResponse, +) from .access_key_retrieve_project_id_response import ( AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse, ) +from .remediation_list_resolved_logs_response import ( + RemediationListResolvedLogsResponse as RemediationListResolvedLogsResponse, +) +from .remediation_get_resolved_logs_count_response import ( + RemediationGetResolvedLogsCountResponse as RemediationGetResolvedLogsCountResponse, +) diff --git a/src/codex/types/projects/eval_create_params.py b/src/codex/types/projects/eval_create_params.py new file mode 100644 index 00000000..20dcdd3d --- /dev/null +++ b/src/codex/types/projects/eval_create_params.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["EvalCreateParams"] + + +class EvalCreateParams(TypedDict, total=False): + criteria: Required[str] + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + is_default: bool + """Whether the eval is a default, built-in eval or a custom eval""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py new file mode 100644 index 00000000..e7f2b1b3 --- /dev/null +++ b/src/codex/types/projects/eval_list_response.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel + +__all__ = ["EvalListResponse", "EvalListResponseItem"] + + +class EvalListResponseItem(BaseModel): + criteria: str + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: str + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: str + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: Optional[bool] = None + """Allows the evaluation to be disabled without removing it""" + + is_default: Optional[bool] = None + """Whether the eval is a default, built-in eval or a custom eval""" + + priority: Optional[int] = None + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] = None + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: Optional[bool] = None + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: Optional[float] = None + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Optional[Literal["above", "below"]] = None + """Whether the evaluation fails when score is above or below the threshold""" + + +EvalListResponse: TypeAlias = List[EvalListResponseItem] diff --git a/src/codex/types/projects/eval_update_params.py b/src/codex/types/projects/eval_update_params.py new file mode 100644 index 00000000..b690ec43 --- /dev/null +++ b/src/codex/types/projects/eval_update_params.py @@ -0,0 +1,104 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["EvalUpdateParams", "CustomEvalCreateOrUpdateSchema", "DefaultEvalUpdateSchema"] + + +class CustomEvalCreateOrUpdateSchema(TypedDict, total=False): + project_id: Required[str] + + criteria: Required[str] + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + body_eval_key: Required[Annotated[str, PropertyInfo(alias="eval_key")]] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + is_default: bool + """Whether the eval is a default, built-in eval or a custom eval""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class DefaultEvalUpdateSchema(TypedDict, total=False): + project_id: Required[str] + + body_eval_key: Required[Annotated[str, PropertyInfo(alias="eval_key")]] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the response is considered bad and can trigger + escalation to Codex/SME + """ + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +EvalUpdateParams: TypeAlias = Union[CustomEvalCreateOrUpdateSchema, DefaultEvalUpdateSchema] diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py new file mode 100644 index 00000000..66166a19 --- /dev/null +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["QueryLogListByGroupParams"] + + +class QueryLogListByGroupParams(TypedDict, total=False): + created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter logs created at or before this timestamp""" + + created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter logs created at or after this timestamp""" + + custom_metadata: Optional[str] + """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + """Filter logs that have ANY of these primary evaluation issues (OR operation)""" + + remediation_ids: List[str] + """List of groups to list child logs for""" + + sort: Optional[Literal["created_at", "primary_eval_issue_score"]] + + was_cache_hit: Optional[bool] + """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py new file mode 100644 index 00000000..ee79b6fa --- /dev/null +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -0,0 +1,96 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = [ + "QueryLogListByGroupResponse", + "QueryLogsByGroup", + "QueryLogsByGroupQueryLog", + "QueryLogsByGroupQueryLogContext", +] + + +class QueryLogsByGroupQueryLogContext(BaseModel): + content: str + """The actual content/text of the document.""" + + id: Optional[str] = None + """Unique identifier for the document. Useful for tracking documents""" + + source: Optional[str] = None + """Source or origin of the document. Useful for citations.""" + + tags: Optional[List[str]] = None + """Tags or categories for the document. Useful for filtering""" + + title: Optional[str] = None + """Title or heading of the document. Useful for display and context.""" + + +class QueryLogsByGroupQueryLog(BaseModel): + id: str + + created_at: datetime + + formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + """Format evaluation scores for frontend display with pass/fail status. + + Returns: Dictionary mapping eval keys to their formatted representation: { + "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if + eval_scores is None. + """ + + is_bad_response: bool + """If an eval with should_escalate=True failed""" + + project_id: str + + question: str + + remediation_id: str + + was_cache_hit: Optional[bool] = None + """If similar query already answered, or None if cache was not checked""" + + context: Optional[List[QueryLogsByGroupQueryLogContext]] = None + """RAG context used for the query""" + + custom_metadata: Optional[object] = None + """Arbitrary metadata supplied by the user/system""" + + custom_metadata_keys: Optional[List[str]] = None + """Keys of the custom metadata""" + + eval_issue_labels: Optional[List[str]] = None + """Labels derived from evaluation scores""" + + eval_scores: Optional[Dict[str, float]] = None + """Evaluation scores for the original response""" + + eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None + """Evaluation thresholds and directions at time of creation""" + + evaluated_response: Optional[str] = None + """The response being evaluated from the RAG system (before any remediation)""" + + primary_eval_issue: Optional[str] = None + """Primary issue identified in evaluation""" + + primary_eval_issue_score: Optional[float] = None + """Score of the primary eval issue""" + + +class QueryLogsByGroup(BaseModel): + query_logs: List[QueryLogsByGroupQueryLog] + + total_count: int + + +class QueryLogListByGroupResponse(BaseModel): + custom_metadata_columns: List[str] + + query_logs_by_group: Dict[str, QueryLogsByGroup] diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py new file mode 100644 index 00000000..558ac0b2 --- /dev/null +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["QueryLogListGroupsParams"] + + +class QueryLogListGroupsParams(TypedDict, total=False): + created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter logs created at or before this timestamp""" + + created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter logs created at or after this timestamp""" + + custom_metadata: Optional[str] + """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + """Filter logs that have ANY of these primary evaluation issues (OR operation)""" + + sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] + + was_cache_hit: Optional[bool] + """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py new file mode 100644 index 00000000..979d34d6 --- /dev/null +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -0,0 +1,91 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["QueryLogListGroupsResponse", "QueryLogGroup", "QueryLogGroupContext"] + + +class QueryLogGroupContext(BaseModel): + content: str + """The actual content/text of the document.""" + + id: Optional[str] = None + """Unique identifier for the document. Useful for tracking documents""" + + source: Optional[str] = None + """Source or origin of the document. Useful for citations.""" + + tags: Optional[List[str]] = None + """Tags or categories for the document. Useful for filtering""" + + title: Optional[str] = None + """Title or heading of the document. Useful for display and context.""" + + +class QueryLogGroup(BaseModel): + id: str + + created_at: datetime + + formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + """Format evaluation scores for frontend display with pass/fail status. + + Returns: Dictionary mapping eval keys to their formatted representation: { + "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if + eval_scores is None. + """ + + is_bad_response: bool + """If an eval with should_escalate=True failed""" + + project_id: str + + question: str + + remediation_id: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + total_count: int + + was_cache_hit: Optional[bool] = None + """If similar query already answered, or None if cache was not checked""" + + context: Optional[List[QueryLogGroupContext]] = None + """RAG context used for the query""" + + custom_metadata: Optional[object] = None + """Arbitrary metadata supplied by the user/system""" + + custom_metadata_keys: Optional[List[str]] = None + """Keys of the custom metadata""" + + eval_issue_labels: Optional[List[str]] = None + """Labels derived from evaluation scores""" + + eval_scores: Optional[Dict[str, float]] = None + """Evaluation scores for the original response""" + + eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None + """Evaluation thresholds and directions at time of creation""" + + evaluated_response: Optional[str] = None + """The response being evaluated from the RAG system (before any remediation)""" + + primary_eval_issue: Optional[str] = None + """Primary issue identified in evaluation""" + + primary_eval_issue_score: Optional[float] = None + """Score of the primary eval issue""" + + +class QueryLogListGroupsResponse(BaseModel): + custom_metadata_columns: List[str] + + query_log_groups: List[QueryLogGroup] + + total_count: int diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py new file mode 100644 index 00000000..9cf3211f --- /dev/null +++ b/src/codex/types/projects/query_log_list_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["QueryLogListParams"] + + +class QueryLogListParams(TypedDict, total=False): + created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter logs created at or before this timestamp""" + + created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter logs created at or after this timestamp""" + + custom_metadata: Optional[str] + """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + ] + """Filter logs that have ANY of these primary evaluation issues (OR operation)""" + + sort: Optional[Literal["created_at", "primary_eval_issue_score"]] + + was_cache_hit: Optional[bool] + """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py new file mode 100644 index 00000000..d570ea50 --- /dev/null +++ b/src/codex/types/projects/query_log_list_response.py @@ -0,0 +1,87 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["QueryLogListResponse", "QueryLog", "QueryLogContext"] + + +class QueryLogContext(BaseModel): + content: str + """The actual content/text of the document.""" + + id: Optional[str] = None + """Unique identifier for the document. Useful for tracking documents""" + + source: Optional[str] = None + """Source or origin of the document. Useful for citations.""" + + tags: Optional[List[str]] = None + """Tags or categories for the document. Useful for filtering""" + + title: Optional[str] = None + """Title or heading of the document. Useful for display and context.""" + + +class QueryLog(BaseModel): + id: str + + created_at: datetime + + formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + """Format evaluation scores for frontend display with pass/fail status. + + Returns: Dictionary mapping eval keys to their formatted representation: { + "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if + eval_scores is None. + """ + + is_bad_response: bool + """If an eval with should_escalate=True failed""" + + project_id: str + + question: str + + remediation_id: str + + was_cache_hit: Optional[bool] = None + """If similar query already answered, or None if cache was not checked""" + + context: Optional[List[QueryLogContext]] = None + """RAG context used for the query""" + + custom_metadata: Optional[object] = None + """Arbitrary metadata supplied by the user/system""" + + custom_metadata_keys: Optional[List[str]] = None + """Keys of the custom metadata""" + + eval_issue_labels: Optional[List[str]] = None + """Labels derived from evaluation scores""" + + eval_scores: Optional[Dict[str, float]] = None + """Evaluation scores for the original response""" + + eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None + """Evaluation thresholds and directions at time of creation""" + + evaluated_response: Optional[str] = None + """The response being evaluated from the RAG system (before any remediation)""" + + primary_eval_issue: Optional[str] = None + """Primary issue identified in evaluation""" + + primary_eval_issue_score: Optional[float] = None + """Score of the primary eval issue""" + + +class QueryLogListResponse(BaseModel): + custom_metadata_columns: List[str] + + query_logs: List[QueryLog] + + total_count: int diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py new file mode 100644 index 00000000..f918c214 --- /dev/null +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -0,0 +1,79 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["QueryLogRetrieveResponse", "Context"] + + +class Context(BaseModel): + content: str + """The actual content/text of the document.""" + + id: Optional[str] = None + """Unique identifier for the document. Useful for tracking documents""" + + source: Optional[str] = None + """Source or origin of the document. Useful for citations.""" + + tags: Optional[List[str]] = None + """Tags or categories for the document. Useful for filtering""" + + title: Optional[str] = None + """Title or heading of the document. Useful for display and context.""" + + +class QueryLogRetrieveResponse(BaseModel): + id: str + + created_at: datetime + + formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + """Format evaluation scores for frontend display with pass/fail status. + + Returns: Dictionary mapping eval keys to their formatted representation: { + "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if + eval_scores is None. + """ + + is_bad_response: bool + """If an eval with should_escalate=True failed""" + + project_id: str + + question: str + + remediation_id: str + + was_cache_hit: Optional[bool] = None + """If similar query already answered, or None if cache was not checked""" + + context: Optional[List[Context]] = None + """RAG context used for the query""" + + custom_metadata: Optional[object] = None + """Arbitrary metadata supplied by the user/system""" + + custom_metadata_keys: Optional[List[str]] = None + """Keys of the custom metadata""" + + eval_issue_labels: Optional[List[str]] = None + """Labels derived from evaluation scores""" + + eval_scores: Optional[Dict[str, float]] = None + """Evaluation scores for the original response""" + + eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None + """Evaluation thresholds and directions at time of creation""" + + evaluated_response: Optional[str] = None + """The response being evaluated from the RAG system (before any remediation)""" + + primary_eval_issue: Optional[str] = None + """Primary issue identified in evaluation""" + + primary_eval_issue_score: Optional[float] = None + """Score of the primary eval issue""" diff --git a/src/codex/types/projects/query_log_start_remediation_response.py b/src/codex/types/projects/query_log_start_remediation_response.py new file mode 100644 index 00000000..0250fb15 --- /dev/null +++ b/src/codex/types/projects/query_log_start_remediation_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["QueryLogStartRemediationResponse"] + + +class QueryLogStartRemediationResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_create_params.py b/src/codex/types/projects/remediation_create_params.py new file mode 100644 index 00000000..0d347490 --- /dev/null +++ b/src/codex/types/projects/remediation_create_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["RemediationCreateParams"] + + +class RemediationCreateParams(TypedDict, total=False): + question: Required[str] + + answer: Optional[str] + + draft_answer: Optional[str] diff --git a/src/codex/types/projects/remediation_create_response.py b/src/codex/types/projects/remediation_create_response.py new file mode 100644 index 00000000..ad4e6893 --- /dev/null +++ b/src/codex/types/projects/remediation_create_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationCreateResponse"] + + +class RemediationCreateResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_edit_answer_params.py b/src/codex/types/projects/remediation_edit_answer_params.py new file mode 100644 index 00000000..0dbc7d8d --- /dev/null +++ b/src/codex/types/projects/remediation_edit_answer_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RemediationEditAnswerParams"] + + +class RemediationEditAnswerParams(TypedDict, total=False): + project_id: Required[str] + + answer: Required[str] diff --git a/src/codex/types/projects/remediation_edit_answer_response.py b/src/codex/types/projects/remediation_edit_answer_response.py new file mode 100644 index 00000000..d8b34323 --- /dev/null +++ b/src/codex/types/projects/remediation_edit_answer_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationEditAnswerResponse"] + + +class RemediationEditAnswerResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_edit_draft_answer_params.py b/src/codex/types/projects/remediation_edit_draft_answer_params.py new file mode 100644 index 00000000..9f51ec6f --- /dev/null +++ b/src/codex/types/projects/remediation_edit_draft_answer_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RemediationEditDraftAnswerParams"] + + +class RemediationEditDraftAnswerParams(TypedDict, total=False): + project_id: Required[str] + + draft_answer: Required[str] diff --git a/src/codex/types/projects/remediation_edit_draft_answer_response.py b/src/codex/types/projects/remediation_edit_draft_answer_response.py new file mode 100644 index 00000000..828035e8 --- /dev/null +++ b/src/codex/types/projects/remediation_edit_draft_answer_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationEditDraftAnswerResponse"] + + +class RemediationEditDraftAnswerResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py new file mode 100644 index 00000000..79997b09 --- /dev/null +++ b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationGetResolvedLogsCountResponse"] + + +class RemediationGetResolvedLogsCountResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + resolved_logs_count: int + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_list_params.py b/src/codex/types/projects/remediation_list_params.py new file mode 100644 index 00000000..65cd08d0 --- /dev/null +++ b/src/codex/types/projects/remediation_list_params.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["RemediationListParams"] + + +class RemediationListParams(TypedDict, total=False): + created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations created at or before this timestamp""" + + created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations created at or after this timestamp""" + + last_edited_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations last edited at or before this timestamp""" + + last_edited_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations last edited at or after this timestamp""" + + last_edited_by: Optional[str] + """Filter by last edited by user ID""" + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] + + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] + """Filter remediations that have ANY of these statuses (OR operation)""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py new file mode 100644 index 00000000..6181028d --- /dev/null +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -0,0 +1,85 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationListResolvedLogsResponse", "QueryLog", "QueryLogContext"] + + +class QueryLogContext(BaseModel): + content: str + """The actual content/text of the document.""" + + id: Optional[str] = None + """Unique identifier for the document. Useful for tracking documents""" + + source: Optional[str] = None + """Source or origin of the document. Useful for citations.""" + + tags: Optional[List[str]] = None + """Tags or categories for the document. Useful for filtering""" + + title: Optional[str] = None + """Title or heading of the document. Useful for display and context.""" + + +class QueryLog(BaseModel): + id: str + + created_at: datetime + + formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + """Format evaluation scores for frontend display with pass/fail status. + + Returns: Dictionary mapping eval keys to their formatted representation: { + "eval_key": { "score": float, "status": "pass" | "fail" } } Returns None if + eval_scores is None. + """ + + is_bad_response: bool + """If an eval with should_escalate=True failed""" + + project_id: str + + question: str + + remediation_id: str + + was_cache_hit: Optional[bool] = None + """If similar query already answered, or None if cache was not checked""" + + context: Optional[List[QueryLogContext]] = None + """RAG context used for the query""" + + custom_metadata: Optional[object] = None + """Arbitrary metadata supplied by the user/system""" + + custom_metadata_keys: Optional[List[str]] = None + """Keys of the custom metadata""" + + eval_issue_labels: Optional[List[str]] = None + """Labels derived from evaluation scores""" + + eval_scores: Optional[Dict[str, float]] = None + """Evaluation scores for the original response""" + + eval_thresholds: Optional[Dict[str, Dict[str, Union[float, str]]]] = None + """Evaluation thresholds and directions at time of creation""" + + evaluated_response: Optional[str] = None + """The response being evaluated from the RAG system (before any remediation)""" + + primary_eval_issue: Optional[str] = None + """Primary issue identified in evaluation""" + + primary_eval_issue_score: Optional[float] = None + """Score of the primary eval issue""" + + +class RemediationListResolvedLogsResponse(BaseModel): + query_logs: List[QueryLog] + + total_count: int diff --git a/src/codex/types/projects/remediation_list_response.py b/src/codex/types/projects/remediation_list_response.py new file mode 100644 index 00000000..3e737970 --- /dev/null +++ b/src/codex/types/projects/remediation_list_response.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationListResponse", "Remediation"] + + +class Remediation(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + resolved_logs_count: int + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + +class RemediationListResponse(BaseModel): + remediations: List[Remediation] + + total_count: int diff --git a/src/codex/types/projects/remediation_pause_response.py b/src/codex/types/projects/remediation_pause_response.py new file mode 100644 index 00000000..ae453581 --- /dev/null +++ b/src/codex/types/projects/remediation_pause_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationPauseResponse"] + + +class RemediationPauseResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_publish_response.py b/src/codex/types/projects/remediation_publish_response.py new file mode 100644 index 00000000..5eb2c622 --- /dev/null +++ b/src/codex/types/projects/remediation_publish_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationPublishResponse"] + + +class RemediationPublishResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_retrieve_response.py b/src/codex/types/projects/remediation_retrieve_response.py new file mode 100644 index 00000000..6fbd60d2 --- /dev/null +++ b/src/codex/types/projects/remediation_retrieve_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationRetrieveResponse"] + + +class RemediationRetrieveResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_unpause_response.py b/src/codex/types/projects/remediation_unpause_response.py new file mode 100644 index 00000000..789484cd --- /dev/null +++ b/src/codex/types/projects/remediation_unpause_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RemediationUnpauseResponse"] + + +class RemediationUnpauseResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + project_id: str + + question: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py new file mode 100644 index 00000000..c16fbc37 --- /dev/null +++ b/tests/api_resources/projects/test_evals.py @@ -0,0 +1,679 @@ +# 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 codex import Codex, AsyncCodex +from codex.types import ProjectReturnSchema +from tests.utils import assert_matches_type +from codex.types.projects import EvalListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEvals: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_create(self, client: Codex) -> None: + eval = client.projects.evals.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + eval = client.projects.evals.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + context_identifier="context_identifier", + enabled=True, + is_default=True, + priority=0, + query_identifier="query_identifier", + response_identifier="response_identifier", + should_escalate=True, + threshold=0, + threshold_direction="above", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.evals.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.evals.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_create(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.evals.with_raw_response.create( + project_id="", + criteria="criteria", + eval_key="eval_key", + name="name", + ) + + @pytest.mark.skip() + @parametrize + def test_method_update_overload_1(self, client: Codex) -> None: + eval = client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_update_with_all_params_overload_1(self, client: Codex) -> None: + eval = client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + context_identifier="context_identifier", + enabled=True, + is_default=True, + priority=0, + query_identifier="query_identifier", + response_identifier="response_identifier", + should_escalate=True, + threshold=0, + threshold_direction="above", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_update_overload_1(self, client: Codex) -> None: + response = client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_update_overload_1(self, client: Codex) -> None: + with client.projects.evals.with_streaming_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_update_overload_1(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"): + client.projects.evals.with_raw_response.update( + path_eval_key="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + + @pytest.mark.skip() + @parametrize + def test_method_update_overload_2(self, client: Codex) -> None: + eval = client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_update_with_all_params_overload_2(self, client: Codex) -> None: + eval = client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + enabled=True, + priority=0, + should_escalate=True, + threshold=0, + threshold_direction="above", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_update_overload_2(self, client: Codex) -> None: + response = client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_update_overload_2(self, client: Codex) -> None: + with client.projects.evals.with_streaming_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_update_overload_2(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="", + body_eval_key="eval_key", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"): + client.projects.evals.with_raw_response.update( + path_eval_key="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) + + @pytest.mark.skip() + @parametrize + def test_method_list(self, client: Codex) -> None: + eval = client.projects.evals.list( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(EvalListResponse, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.evals.with_raw_response.list( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(EvalListResponse, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.evals.with_streaming_response.list( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(EvalListResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.evals.with_raw_response.list( + "", + ) + + @pytest.mark.skip() + @parametrize + def test_method_delete(self, client: Codex) -> None: + eval = client.projects.evals.delete( + eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.evals.with_raw_response.delete( + eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.evals.with_streaming_response.delete( + eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.evals.with_raw_response.delete( + eval_key="eval_key", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_key` but received ''"): + client.projects.evals.with_raw_response.delete( + eval_key="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncEvals: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + context_identifier="context_identifier", + enabled=True, + is_default=True, + priority=0, + query_identifier="query_identifier", + response_identifier="response_identifier", + should_escalate=True, + threshold=0, + threshold_direction="above", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.evals.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.evals.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + eval_key="eval_key", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_create(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.evals.with_raw_response.create( + project_id="", + criteria="criteria", + eval_key="eval_key", + name="name", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_update_overload_1(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_update_with_all_params_overload_1(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + context_identifier="context_identifier", + enabled=True, + is_default=True, + priority=0, + query_identifier="query_identifier", + response_identifier="response_identifier", + should_escalate=True, + threshold=0, + threshold_direction="above", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_update_overload_1(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_update_overload_1(self, async_client: AsyncCodex) -> None: + async with async_client.projects.evals.with_streaming_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_update_overload_1(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"): + await async_client.projects.evals.with_raw_response.update( + path_eval_key="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + criteria="criteria", + body_eval_key="eval_key", + name="name", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_update_overload_2(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_update_with_all_params_overload_2(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + enabled=True, + priority=0, + should_escalate=True, + threshold=0, + threshold_direction="above", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_update_overload_2(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_update_overload_2(self, async_client: AsyncCodex) -> None: + async with async_client.projects.evals.with_streaming_response.update( + path_eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_update_overload_2(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.evals.with_raw_response.update( + path_eval_key="eval_key", + project_id="", + body_eval_key="eval_key", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_eval_key` but received ''"): + await async_client.projects.evals.with_raw_response.update( + path_eval_key="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body_eval_key="eval_key", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.list( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(EvalListResponse, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.evals.with_raw_response.list( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = await response.parse() + assert_matches_type(EvalListResponse, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.evals.with_streaming_response.list( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(EvalListResponse, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.evals.with_raw_response.list( + "", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.delete( + eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.evals.with_raw_response.delete( + eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.evals.with_streaming_response.delete( + eval_key="eval_key", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + eval = await response.parse() + assert_matches_type(ProjectReturnSchema, eval, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.evals.with_raw_response.delete( + eval_key="eval_key", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `eval_key` but received ''"): + await async_client.projects.evals.with_raw_response.delete( + eval_key="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py new file mode 100644 index 00000000..847af5df --- /dev/null +++ b/tests/api_resources/projects/test_query_logs.py @@ -0,0 +1,593 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex._utils import parse_datetime +from codex.types.projects import ( + QueryLogListResponse, + QueryLogRetrieveResponse, + QueryLogListGroupsResponse, + QueryLogListByGroupResponse, + QueryLogStartRemediationResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestQueryLogs: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + query_log = client.projects.query_logs.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + client.projects.query_logs.with_raw_response.retrieve( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + def test_method_list(self, client: Codex) -> None: + query_log = client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + query_log = client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + limit=1, + offset=0, + order="asc", + primary_eval_issue=["hallucination"], + sort="created_at", + was_cache_hit=True, + ) + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + def test_method_list_by_group(self, client: Codex) -> None: + query_log = client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_by_group_with_all_params(self, client: Codex) -> None: + query_log = client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + limit=1, + offset=0, + order="asc", + primary_eval_issue=["hallucination"], + remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + sort="created_at", + was_cache_hit=True, + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list_by_group(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list_by_group(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list_by_group(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.list_by_group( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + def test_method_list_groups(self, client: Codex) -> None: + query_log = client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_groups_with_all_params(self, client: Codex) -> None: + query_log = client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + limit=1, + offset=0, + order="asc", + primary_eval_issue=["hallucination"], + sort="created_at", + was_cache_hit=True, + ) + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list_groups(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list_groups(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list_groups(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.list_groups( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + def test_method_start_remediation(self, client: Codex) -> None: + query_log = client.projects.query_logs.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_start_remediation(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_start_remediation(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_start_remediation(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncQueryLogs: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.retrieve( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.retrieve( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + limit=1, + offset=0, + order="asc", + primary_eval_issue=["hallucination"], + sort="created_at", + was_cache_hit=True, + ) + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list_by_group(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_by_group_with_all_params(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + limit=1, + offset=0, + order="asc", + primary_eval_issue=["hallucination"], + remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + sort="created_at", + was_cache_hit=True, + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list_by_group(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list_by_group(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.list_by_group( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list_groups(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + limit=1, + offset=0, + order="asc", + primary_eval_issue=["hallucination"], + sort="created_at", + was_cache_hit=True, + ) + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list_groups(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list_groups(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.list_groups( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_start_remediation(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_start_remediation(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_start_remediation(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_start_remediation(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py new file mode 100644 index 00000000..cfd5ae76 --- /dev/null +++ b/tests/api_resources/projects/test_remediations.py @@ -0,0 +1,1224 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex._utils import parse_datetime +from codex.types.projects import ( + RemediationListResponse, + RemediationPauseResponse, + RemediationCreateResponse, + RemediationPublishResponse, + RemediationUnpauseResponse, + RemediationRetrieveResponse, + RemediationEditAnswerResponse, + RemediationEditDraftAnswerResponse, + RemediationListResolvedLogsResponse, + RemediationGetResolvedLogsCountResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRemediations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_create(self, client: Codex) -> None: + remediation = client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + remediation = client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + answer="answer", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_create(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.create( + project_id="", + question="question", + ) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + remediation = client.projects.remediations.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.retrieve( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + def test_method_list(self, client: Codex) -> None: + remediation = client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + remediation = client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + sort="created_at", + status=["ACTIVE"], + ) + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + def test_method_delete(self, client: Codex) -> None: + remediation = client.projects.remediations.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert remediation is None + + @pytest.mark.skip() + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert remediation is None + + @pytest.mark.skip() + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert remediation is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.delete( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + def test_method_edit_answer(self, client: Codex) -> None: + remediation = client.projects.remediations.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_edit_answer(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_edit_answer(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_edit_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + answer="answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.edit_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + @pytest.mark.skip() + @parametrize + def test_method_edit_draft_answer(self, client: Codex) -> None: + remediation = client.projects.remediations.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_edit_draft_answer(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_edit_draft_answer(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_edit_draft_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + draft_answer="draft_answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + @pytest.mark.skip() + @parametrize + def test_method_get_resolved_logs_count(self, client: Codex) -> None: + remediation = client.projects.remediations.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_get_resolved_logs_count(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_get_resolved_logs_count(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + def test_method_list_resolved_logs(self, client: Codex) -> None: + remediation = client.projects.remediations.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list_resolved_logs(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list_resolved_logs(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list_resolved_logs(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + def test_method_pause(self, client: Codex) -> None: + remediation = client.projects.remediations.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_pause(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_pause(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_pause(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.pause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + def test_method_publish(self, client: Codex) -> None: + remediation = client.projects.remediations.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_publish(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_publish(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_publish(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.publish( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + def test_method_unpause(self, client: Codex) -> None: + remediation = client.projects.remediations.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_unpause(self, client: Codex) -> None: + response = client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = response.parse() + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_unpause(self, client: Codex) -> None: + with client.projects.remediations.with_streaming_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = response.parse() + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_unpause(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.unpause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncRemediations: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + answer="answer", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="question", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_create(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.create( + project_id="", + question="question", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.retrieve( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + sort="created_at", + status=["ACTIVE"], + ) + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationListResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert remediation is None + + @pytest.mark.skip() + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert remediation is None + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert remediation is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.delete( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_edit_answer(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + answer="answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + draft_answer="draft_answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_list_resolved_logs(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list_resolved_logs(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list_resolved_logs(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_pause(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_pause(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_pause(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.pause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_publish(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_publish(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_publish(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_publish(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.publish( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_unpause(self, async_client: AsyncCodex) -> None: + remediation = await async_client.projects.remediations.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + remediation = await response.parse() + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.with_streaming_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + remediation = await response.parse() + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_unpause(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.unpause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index c2b5b7db..86bd8053 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -13,6 +13,7 @@ ProjectReturnSchema, ProjectRetrieveResponse, ProjectValidateResponse, + ProjectInviteSmeResponse, ProjectRetrieveAnalyticsResponse, ) from tests.utils import assert_matches_type @@ -515,6 +516,60 @@ def test_path_params_increment_queries(self, client: Codex) -> None: project_id="", ) + @pytest.mark.skip() + @parametrize + def test_method_invite_sme(self, client: Codex) -> None: + project = client.projects.invite_sme( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) + assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_invite_sme(self, client: Codex) -> None: + response = client.projects.with_raw_response.invite_sme( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_invite_sme(self, client: Codex) -> None: + with client.projects.with_streaming_response.invite_sme( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_invite_sme(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.invite_sme( + project_id="", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) + @pytest.mark.skip() @parametrize def test_method_retrieve_analytics(self, client: Codex) -> None: @@ -1160,6 +1215,60 @@ async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> project_id="", ) + @pytest.mark.skip() + @parametrize + async def test_method_invite_sme(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.invite_sme( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) + assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_invite_sme(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.invite_sme( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_invite_sme(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.invite_sme( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_invite_sme(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.invite_sme( + project_id="", + email="email", + page_type="query_log", + url_query_string="url_query_string", + ) + @pytest.mark.skip() @parametrize async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None: From 407fce98af9d619ffdc2034f9a1e4f1d57deabd2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 23:35:06 +0000 Subject: [PATCH 172/320] feat(api): add bearer token auth --- .stats.yml | 2 +- README.md | 2 ++ src/codex/_client.py | 44 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index bd77f787..20dc969f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 65 openapi_spec_hash: eeb8ebc5600523bdfad046381a929572 -config_hash: 63e520502003839482d0dbeb82132064 +config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/README.md b/README.md index ae489b6e..2e04092c 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ The full API of this library can be found in [api.md](api.md). from codex import Codex client = Codex( + auth_token="My Auth Token", # or 'production' | 'local'; defaults to "production". environment="staging", ) @@ -48,6 +49,7 @@ import asyncio from codex import AsyncCodex client = AsyncCodex( + auth_token="My Auth Token", # or 'production' | 'local'; defaults to "production". environment="staging", ) diff --git a/src/codex/_client.py b/src/codex/_client.py index 7464f6dc..352d0dbc 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -63,6 +63,7 @@ class Codex(SyncAPIClient): with_streaming_response: CodexWithStreamedResponse # client options + auth_token: str | None api_key: str | None access_key: str | None @@ -71,6 +72,7 @@ class Codex(SyncAPIClient): def __init__( self, *, + auth_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, @@ -94,6 +96,8 @@ def __init__( _strict_response_validation: bool = False, ) -> None: """Construct a new synchronous Codex client instance.""" + self.auth_token = auth_token + self.api_key = api_key self.access_key = access_key @@ -151,7 +155,14 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - return {**self._authenticated_api_key, **self._public_access_key} + return {**self._http_bearer, **self._authenticated_api_key, **self._public_access_key} + + @property + def _http_bearer(self) -> dict[str, str]: + auth_token = self.auth_token + if auth_token is None: + return {} + return {"Authorization": f"Bearer {auth_token}"} @property def _authenticated_api_key(self) -> dict[str, str]: @@ -178,6 +189,11 @@ def default_headers(self) -> dict[str, str | Omit]: @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.auth_token and headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + if self.api_key and headers.get("X-API-Key"): return if isinstance(custom_headers.get("X-API-Key"), Omit): @@ -189,12 +205,13 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: return raise TypeError( - '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + '"Could not resolve authentication method. Expected one of auth_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' ) def copy( self, *, + auth_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | None = None, @@ -231,6 +248,7 @@ def copy( http_client = http_client or self._client return self.__class__( + auth_token=auth_token or self.auth_token, api_key=api_key or self.api_key, access_key=access_key or self.access_key, base_url=base_url or self.base_url, @@ -291,6 +309,7 @@ class AsyncCodex(AsyncAPIClient): with_streaming_response: AsyncCodexWithStreamedResponse # client options + auth_token: str | None api_key: str | None access_key: str | None @@ -299,6 +318,7 @@ class AsyncCodex(AsyncAPIClient): def __init__( self, *, + auth_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, @@ -322,6 +342,8 @@ def __init__( _strict_response_validation: bool = False, ) -> None: """Construct a new async AsyncCodex client instance.""" + self.auth_token = auth_token + self.api_key = api_key self.access_key = access_key @@ -379,7 +401,14 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - return {**self._authenticated_api_key, **self._public_access_key} + return {**self._http_bearer, **self._authenticated_api_key, **self._public_access_key} + + @property + def _http_bearer(self) -> dict[str, str]: + auth_token = self.auth_token + if auth_token is None: + return {} + return {"Authorization": f"Bearer {auth_token}"} @property def _authenticated_api_key(self) -> dict[str, str]: @@ -406,6 +435,11 @@ def default_headers(self) -> dict[str, str | Omit]: @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.auth_token and headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + if self.api_key and headers.get("X-API-Key"): return if isinstance(custom_headers.get("X-API-Key"), Omit): @@ -417,12 +451,13 @@ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: return raise TypeError( - '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + '"Could not resolve authentication method. Expected one of auth_token, api_key or access_key to be set. Or for one of the `Authorization`, `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' ) def copy( self, *, + auth_token: str | None = None, api_key: str | None = None, access_key: str | None = None, environment: Literal["production", "staging", "local"] | None = None, @@ -459,6 +494,7 @@ def copy( http_client = http_client or self._client return self.__class__( + auth_token=auth_token or self.auth_token, api_key=api_key or self.api_key, access_key=access_key or self.access_key, base_url=base_url or self.base_url, From 731656ac936c7a166ebb369b2775074248ea5ced Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 04:02:37 +0000 Subject: [PATCH 173/320] feat(client): add support for aiohttp --- README.md | 35 +++++++++++++++++ pyproject.toml | 2 + requirements-dev.lock | 27 +++++++++++++ requirements.lock | 27 +++++++++++++ src/codex/__init__.py | 3 +- src/codex/_base_client.py | 22 +++++++++++ .../billing/test_card_details.py | 4 +- .../billing/test_plan_details.py | 4 +- .../billing/test_setup_intent.py | 4 +- .../organizations/test_billing.py | 4 +- .../projects/test_access_keys.py | 4 +- tests/api_resources/projects/test_clusters.py | 4 +- tests/api_resources/projects/test_entries.py | 4 +- tests/api_resources/projects/test_evals.py | 4 +- .../api_resources/projects/test_query_logs.py | 4 +- .../projects/test_remediations.py | 4 +- tests/api_resources/test_health.py | 4 +- tests/api_resources/test_organizations.py | 4 +- tests/api_resources/test_projects.py | 4 +- tests/api_resources/test_tlm.py | 4 +- tests/api_resources/test_users.py | 4 +- .../users/myself/test_api_key.py | 4 +- .../users/myself/test_organizations.py | 4 +- tests/api_resources/users/test_myself.py | 4 +- .../api_resources/users/test_verification.py | 4 +- tests/conftest.py | 39 ++++++++++++++++--- 26 files changed, 206 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 2e04092c..74f841bf 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,41 @@ 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 --pre codex-sdk[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import asyncio +from codex import DefaultAioHttpClient +from codex import AsyncCodex + + +async def main() -> None: + async with AsyncCodex( + auth_token="My Auth Token", + http_client=DefaultAioHttpClient(), + ) as client: + project_return_schema = await client.projects.create( + config={}, + name="name", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + print(project_return_schema.id) + + +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: diff --git a/pyproject.toml b/pyproject.toml index 68924033..bad25395 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ classifiers = [ Homepage = "https://github.com/cleanlab/codex-python" Repository = "https://github.com/cleanlab/codex-python" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index 9e127b74..11a27038 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,6 +10,13 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via codex-sdk + # via httpx-aiohttp +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 @@ -17,6 +24,10 @@ anyio==4.4.0 # via httpx argcomplete==3.1.2 # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -34,16 +45,23 @@ execnet==2.1.1 # via pytest-xdist filelock==3.12.4 # via virtualenv +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 # via codex-sdk + # via httpx-aiohttp # via respx +httpx-aiohttp==0.1.6 + # via codex-sdk idna==3.4 # via anyio # via httpx + # via yarl importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest @@ -51,6 +69,9 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py +multidict==6.4.4 + # via aiohttp + # via yarl mypy==1.14.1 mypy-extensions==1.0.0 # via mypy @@ -65,6 +86,9 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via codex-sdk pydantic-core==2.27.1 @@ -98,11 +122,14 @@ tomli==2.0.2 typing-extensions==4.12.2 # via anyio # via codex-sdk + # via multidict # via mypy # via pydantic # via pydantic-core # via pyright virtualenv==20.24.5 # via nox +yarl==1.20.0 + # via aiohttp zipp==3.17.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 1f6c5f7f..5eda9d9c 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,11 +10,22 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via codex-sdk + # via httpx-aiohttp +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 # via codex-sdk # via httpx +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -22,15 +33,28 @@ distro==1.8.0 # via codex-sdk exceptiongroup==1.2.2 # via anyio +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 # via codex-sdk + # via httpx-aiohttp +httpx-aiohttp==0.1.6 + # via codex-sdk idna==3.4 # via anyio # via httpx + # via yarl +multidict==6.4.4 + # via aiohttp + # via yarl +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via codex-sdk pydantic-core==2.27.1 @@ -41,5 +65,8 @@ sniffio==1.3.0 typing-extensions==4.12.2 # via anyio # via codex-sdk + # via multidict # via pydantic # via pydantic-core +yarl==1.20.0 + # via aiohttp diff --git a/src/codex/__init__.py b/src/codex/__init__.py index 5c5f678c..2b07cd12 100644 --- a/src/codex/__init__.py +++ b/src/codex/__init__.py @@ -37,7 +37,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__ = [ @@ -80,6 +80,7 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] if not _t.TYPE_CHECKING: diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 6ef7dd5f..1eca89e0 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -1289,6 +1289,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 @@ -1297,8 +1315,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): diff --git a/tests/api_resources/organizations/billing/test_card_details.py b/tests/api_resources/organizations/billing/test_card_details.py index 2fb71fc5..3a034833 100644 --- a/tests/api_resources/organizations/billing/test_card_details.py +++ b/tests/api_resources/organizations/billing/test_card_details.py @@ -61,7 +61,9 @@ def test_path_params_retrieve(self, client: Codex) -> None: class TestAsyncCardDetails: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/organizations/billing/test_plan_details.py b/tests/api_resources/organizations/billing/test_plan_details.py index 2fc1b810..76d9732e 100644 --- a/tests/api_resources/organizations/billing/test_plan_details.py +++ b/tests/api_resources/organizations/billing/test_plan_details.py @@ -61,7 +61,9 @@ def test_path_params_retrieve(self, client: Codex) -> None: class TestAsyncPlanDetails: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/organizations/billing/test_setup_intent.py b/tests/api_resources/organizations/billing/test_setup_intent.py index 3eb05fbc..49d80b0d 100644 --- a/tests/api_resources/organizations/billing/test_setup_intent.py +++ b/tests/api_resources/organizations/billing/test_setup_intent.py @@ -61,7 +61,9 @@ def test_path_params_create(self, client: Codex) -> None: class TestAsyncSetupIntent: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py index e3bb1d1d..237562b5 100644 --- a/tests/api_resources/organizations/test_billing.py +++ b/tests/api_resources/organizations/test_billing.py @@ -103,7 +103,9 @@ def test_path_params_usage(self, client: Codex) -> None: class TestAsyncBilling: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index ad4ee5e4..c3bc1785 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -380,7 +380,9 @@ def test_path_params_revoke(self, client: Codex) -> None: class TestAsyncAccessKeys: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py index 496236f8..87734277 100644 --- a/tests/api_resources/projects/test_clusters.py +++ b/tests/api_resources/projects/test_clusters.py @@ -131,7 +131,9 @@ def test_path_params_list_variants(self, client: Codex) -> None: class TestAsyncClusters: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index eb6fd372..1b077d10 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -517,7 +517,9 @@ def test_path_params_unpublish_answer(self, client: Codex) -> None: class TestAsyncEntries: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py index c16fbc37..1fd4216f 100644 --- a/tests/api_resources/projects/test_evals.py +++ b/tests/api_resources/projects/test_evals.py @@ -348,7 +348,9 @@ def test_path_params_delete(self, client: Codex) -> None: class TestAsyncEvals: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 847af5df..d75dcabe 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -308,7 +308,9 @@ def test_path_params_start_remediation(self, client: Codex) -> None: class TestAsyncQueryLogs: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py index cfd5ae76..c75a255c 100644 --- a/tests/api_resources/projects/test_remediations.py +++ b/tests/api_resources/projects/test_remediations.py @@ -626,7 +626,9 @@ def test_path_params_unpause(self, client: Codex) -> None: class TestAsyncRemediations: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py index ed942cd2..92db3a81 100644 --- a/tests/api_resources/test_health.py +++ b/tests/api_resources/test_health.py @@ -75,7 +75,9 @@ def test_streaming_response_db(self, client: Codex) -> None: class TestAsyncHealth: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index c665e85c..eecdf3f7 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -149,7 +149,9 @@ def test_path_params_retrieve_permissions(self, client: Codex) -> None: class TestAsyncOrganizations: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 86bd8053..6c95f273 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -723,7 +723,9 @@ def test_path_params_validate(self, client: Codex) -> None: class TestAsyncProjects: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index 32d5a67f..41376a46 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -135,7 +135,9 @@ def test_streaming_response_score(self, client: Codex) -> None: class TestAsyncTlm: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index 101d0f66..661ee559 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -71,7 +71,9 @@ def test_streaming_response_activate_account(self, client: Codex) -> None: class TestAsyncUsers: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index c6ed0209..f0a7ccf7 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -75,7 +75,9 @@ def test_streaming_response_refresh(self, client: Codex) -> None: class TestAsyncAPIKey: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py index 9f35f7c6..fd377ea0 100644 --- a/tests/api_resources/users/myself/test_organizations.py +++ b/tests/api_resources/users/myself/test_organizations.py @@ -47,7 +47,9 @@ def test_streaming_response_list(self, client: Codex) -> None: class TestAsyncOrganizations: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index 63123275..1c56b0be 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -47,7 +47,9 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: class TestAsyncMyself: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/api_resources/users/test_verification.py b/tests/api_resources/users/test_verification.py index 8332327e..fbf6b667 100644 --- a/tests/api_resources/users/test_verification.py +++ b/tests/api_resources/users/test_verification.py @@ -47,7 +47,9 @@ def test_streaming_response_resend(self, client: Codex) -> None: class TestAsyncVerification: - 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"] + ) @pytest.mark.skip() @parametrize diff --git a/tests/conftest.py b/tests/conftest.py index 05c77729..3472c36d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,10 +6,12 @@ import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from codex import Codex, AsyncCodex +from codex import Codex, AsyncCodex, DefaultAioHttpClient +from codex._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -27,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -43,9 +58,23 @@ def client(request: FixtureRequest) -> Iterator[Codex]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") - async with AsyncCodex(base_url=base_url, _strict_response_validation=strict) as client: + async with AsyncCodex(base_url=base_url, _strict_response_validation=strict, http_client=http_client) as client: yield client From 373006432d62627c76b44f15461d23c830aa5509 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 22 Jun 2025 20:09:03 +0000 Subject: [PATCH 174/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fac14074..7c31fce2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.20" + ".": "0.1.0-alpha.21" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index bad25395..d311e3f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.20" +version = "0.1.0-alpha.21" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 44d6131d..3b23c98f 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.20" # x-release-please-version +__version__ = "0.1.0-alpha.21" # x-release-please-version From 8b5b5dc90bc437ed55ddafbd3b620be52335df98 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 04:11:04 +0000 Subject: [PATCH 175/320] chore(tests): skip some failing tests on the latest python versions --- tests/test_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 9cf7e943..c012f4d5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -182,6 +182,7 @@ 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" + @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) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -965,6 +966,7 @@ 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" + @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) -> None: options = FinalRequestOptions(method="get", url="/foo") From 7277b794b56a208f1f1f7a54357d91dce4d428bb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:17:38 +0000 Subject: [PATCH 176/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/evals.py | 48 ++++++++++++++----- src/codex/types/project_create_params.py | 42 +++++++++++----- src/codex/types/project_list_response.py | 42 +++++++++++----- src/codex/types/project_retrieve_response.py | 42 +++++++++++----- src/codex/types/project_return_schema.py | 42 +++++++++++----- src/codex/types/project_update_params.py | 42 +++++++++++----- src/codex/types/project_validate_response.py | 23 ++++++++- .../types/projects/eval_create_params.py | 7 ++- .../types/projects/eval_list_response.py | 7 ++- .../types/projects/eval_update_params.py | 14 ++++-- .../query_log_list_by_group_response.py | 7 ++- .../query_log_list_groups_response.py | 11 ++++- .../types/projects/query_log_list_response.py | 7 ++- .../projects/query_log_retrieve_response.py | 7 ++- .../query_log_start_remediation_response.py | 4 +- .../projects/remediation_create_response.py | 4 +- .../remediation_edit_answer_response.py | 4 +- .../remediation_edit_draft_answer_response.py | 4 +- ...iation_get_resolved_logs_count_response.py | 4 +- ...remediation_list_resolved_logs_response.py | 7 ++- .../projects/remediation_list_response.py | 4 +- .../projects/remediation_pause_response.py | 4 +- .../projects/remediation_publish_response.py | 4 +- .../projects/remediation_retrieve_response.py | 4 +- .../projects/remediation_unpause_response.py | 4 +- tests/api_resources/projects/test_evals.py | 6 +++ tests/api_resources/test_projects.py | 24 ++++++++++ 28 files changed, 321 insertions(+), 99 deletions(-) diff --git a/.stats.yml b/.stats.yml index 20dc969f..04c13865 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 65 -openapi_spec_hash: eeb8ebc5600523bdfad046381a929572 +openapi_spec_hash: 80696dc202de8bacc0e43506d7c210b0 config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py index 1a6a6876..1fc95890 100644 --- a/src/codex/resources/projects/evals.py +++ b/src/codex/resources/projects/evals.py @@ -59,6 +59,7 @@ def create( query_identifier: Optional[str] | NotGiven = NOT_GIVEN, response_identifier: Optional[str] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -95,8 +96,10 @@ def create( response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM response. - should_escalate: If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + should_escalate: If true, failing this eval means the question should be escalated to Codex for + an SME to review + + should_guardrail: If true, failing this eval means the response should be guardrailed threshold: Threshold value that determines if the evaluation fails @@ -126,6 +129,7 @@ def create( "query_identifier": query_identifier, "response_identifier": response_identifier, "should_escalate": should_escalate, + "should_guardrail": should_guardrail, "threshold": threshold, "threshold_direction": threshold_direction, }, @@ -153,6 +157,7 @@ def update( query_identifier: Optional[str] | NotGiven = NOT_GIVEN, response_identifier: Optional[str] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -189,8 +194,10 @@ def update( response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM response. - should_escalate: If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + should_escalate: If true, failing this eval means the question should be escalated to Codex for + an SME to review + + should_guardrail: If true, failing this eval means the response should be guardrailed threshold: Threshold value that determines if the evaluation fails @@ -216,6 +223,7 @@ def update( enabled: bool | NotGiven = NOT_GIVEN, priority: Optional[int] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -237,8 +245,10 @@ def update( priority: Priority order for evals (lower number = higher priority) to determine primary eval issue to surface - should_escalate: If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + should_escalate: If true, failing this eval means the question should be escalated to Codex for + an SME to review + + should_guardrail: If true, failing this eval means the response should be guardrailed threshold: Threshold value that determines if the evaluation fails @@ -270,6 +280,7 @@ def update( query_identifier: Optional[str] | NotGiven = NOT_GIVEN, response_identifier: Optional[str] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -297,6 +308,7 @@ def update( "query_identifier": query_identifier, "response_identifier": response_identifier, "should_escalate": should_escalate, + "should_guardrail": should_guardrail, "threshold": threshold, "threshold_direction": threshold_direction, }, @@ -412,6 +424,7 @@ async def create( query_identifier: Optional[str] | NotGiven = NOT_GIVEN, response_identifier: Optional[str] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -448,8 +461,10 @@ async def create( response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM response. - should_escalate: If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + should_escalate: If true, failing this eval means the question should be escalated to Codex for + an SME to review + + should_guardrail: If true, failing this eval means the response should be guardrailed threshold: Threshold value that determines if the evaluation fails @@ -479,6 +494,7 @@ async def create( "query_identifier": query_identifier, "response_identifier": response_identifier, "should_escalate": should_escalate, + "should_guardrail": should_guardrail, "threshold": threshold, "threshold_direction": threshold_direction, }, @@ -506,6 +522,7 @@ async def update( query_identifier: Optional[str] | NotGiven = NOT_GIVEN, response_identifier: Optional[str] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -542,8 +559,10 @@ async def update( response_identifier: The exact string used in your evaluation criteria to reference the RAG/LLM response. - should_escalate: If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + should_escalate: If true, failing this eval means the question should be escalated to Codex for + an SME to review + + should_guardrail: If true, failing this eval means the response should be guardrailed threshold: Threshold value that determines if the evaluation fails @@ -569,6 +588,7 @@ async def update( enabled: bool | NotGiven = NOT_GIVEN, priority: Optional[int] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -590,8 +610,10 @@ async def update( priority: Priority order for evals (lower number = higher priority) to determine primary eval issue to surface - should_escalate: If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + should_escalate: If true, failing this eval means the question should be escalated to Codex for + an SME to review + + should_guardrail: If true, failing this eval means the response should be guardrailed threshold: Threshold value that determines if the evaluation fails @@ -623,6 +645,7 @@ async def update( query_identifier: Optional[str] | NotGiven = NOT_GIVEN, response_identifier: Optional[str] | NotGiven = NOT_GIVEN, should_escalate: bool | NotGiven = NOT_GIVEN, + should_guardrail: bool | NotGiven = NOT_GIVEN, threshold: float | NotGiven = NOT_GIVEN, threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -650,6 +673,7 @@ async def update( "query_identifier": query_identifier, "response_identifier": response_identifier, "should_escalate": should_escalate, + "should_guardrail": should_guardrail, "threshold": threshold, "threshold_direction": threshold_direction, }, diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index 4d9d31f5..a8369782 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -79,10 +79,13 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -115,10 +118,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -147,10 +153,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -179,10 +188,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -211,10 +223,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -243,10 +258,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index a7df3f38..c39bf086 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -69,10 +69,13 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -105,10 +108,13 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -137,10 +143,13 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -169,10 +178,13 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -201,10 +213,13 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -233,10 +248,13 @@ class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 53624096..7d1f8edd 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -68,10 +68,13 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -104,10 +107,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -136,10 +142,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -168,10 +177,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -200,10 +212,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -232,10 +247,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 2ad7433a..170d7994 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -68,10 +68,13 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -104,10 +107,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -136,10 +142,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -168,10 +177,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -200,10 +212,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" @@ -232,10 +247,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index fc6c52d4..73dad672 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -77,10 +77,13 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -113,10 +116,13 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -145,10 +151,13 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -177,10 +186,13 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -209,10 +221,13 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -241,10 +256,13 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index a88874da..db65f676 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -8,14 +8,27 @@ class EvalScores(BaseModel): - failed: bool - score: Optional[float] = None + triggered: bool + + triggered_escalation: bool + + triggered_guardrail: bool + + failed: Optional[bool] = None + log: Optional[object] = None class ProjectValidateResponse(BaseModel): + escalated_to_sme: bool + """ + True if the question should be escalated to Codex for an SME to review, False + otherwise. When True, a lookup is performed, which logs this query in the + project for SMEs to answer, if it does not already exist. + """ + eval_scores: Dict[str, EvalScores] """ Evaluation scores for the original response along with a boolean flag, `failed`, @@ -34,3 +47,9 @@ class ProjectValidateResponse(BaseModel): When True, a lookup is performed, which logs this query in the project for SMEs to answer, if it does not already exist. """ + + should_guardrail: bool + """ + True if the response should be guardrailed by the AI system, False if the + response is okay to return to the user. + """ diff --git a/src/codex/types/projects/eval_create_params.py b/src/codex/types/projects/eval_create_params.py index 20dcdd3d..aead432e 100644 --- a/src/codex/types/projects/eval_create_params.py +++ b/src/codex/types/projects/eval_create_params.py @@ -55,10 +55,13 @@ class EvalCreateParams(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py index e7f2b1b3..48859b8d 100644 --- a/src/codex/types/projects/eval_list_response.py +++ b/src/codex/types/projects/eval_list_response.py @@ -55,10 +55,13 @@ class EvalListResponseItem(BaseModel): should_escalate: Optional[bool] = None """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: Optional[bool] = None + """If true, failing this eval means the response should be guardrailed""" + threshold: Optional[float] = None """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/projects/eval_update_params.py b/src/codex/types/projects/eval_update_params.py index b690ec43..545c0b29 100644 --- a/src/codex/types/projects/eval_update_params.py +++ b/src/codex/types/projects/eval_update_params.py @@ -59,10 +59,13 @@ class CustomEvalCreateOrUpdateSchema(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" @@ -90,10 +93,13 @@ class DefaultEvalUpdateSchema(TypedDict, total=False): should_escalate: bool """ - If true, failing this eval means the response is considered bad and can trigger - escalation to Codex/SME + If true, failing this eval means the question should be escalated to Codex for + an SME to review """ + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + threshold: float """Threshold value that determines if the evaluation fails""" diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index ee79b6fa..d11d8276 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -45,7 +45,6 @@ class QueryLogsByGroupQueryLog(BaseModel): """ is_bad_response: bool - """If an eval with should_escalate=True failed""" project_id: str @@ -65,6 +64,9 @@ class QueryLogsByGroupQueryLog(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + escalated: Optional[bool] = None + """If true, the question was escalated to Codex for an SME to review""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -77,6 +79,9 @@ class QueryLogsByGroupQueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrailed: Optional[bool] = None + """If true, the response was guardrailed""" + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 979d34d6..fd87c309 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -40,7 +40,8 @@ class QueryLogGroup(BaseModel): """ is_bad_response: bool - """If an eval with should_escalate=True failed""" + + needs_review: bool project_id: str @@ -48,7 +49,7 @@ class QueryLogGroup(BaseModel): remediation_id: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] total_count: int @@ -64,6 +65,9 @@ class QueryLogGroup(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + escalated: Optional[bool] = None + """If true, the question was escalated to Codex for an SME to review""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -76,6 +80,9 @@ class QueryLogGroup(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrailed: Optional[bool] = None + """If true, the response was guardrailed""" + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index d570ea50..bfd37cdd 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -40,7 +40,6 @@ class QueryLog(BaseModel): """ is_bad_response: bool - """If an eval with should_escalate=True failed""" project_id: str @@ -60,6 +59,9 @@ class QueryLog(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + escalated: Optional[bool] = None + """If true, the question was escalated to Codex for an SME to review""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -72,6 +74,9 @@ class QueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrailed: Optional[bool] = None + """If true, the response was guardrailed""" + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index f918c214..3b813eea 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -40,7 +40,6 @@ class QueryLogRetrieveResponse(BaseModel): """ is_bad_response: bool - """If an eval with should_escalate=True failed""" project_id: str @@ -60,6 +59,9 @@ class QueryLogRetrieveResponse(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + escalated: Optional[bool] = None + """If true, the question was escalated to Codex for an SME to review""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -72,6 +74,9 @@ class QueryLogRetrieveResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrailed: Optional[bool] = None + """If true, the response was guardrailed""" + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/query_log_start_remediation_response.py b/src/codex/types/projects/query_log_start_remediation_response.py index 0250fb15..ee7f0c72 100644 --- a/src/codex/types/projects/query_log_start_remediation_response.py +++ b/src/codex/types/projects/query_log_start_remediation_response.py @@ -22,11 +22,13 @@ class QueryLogStartRemediationResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_create_response.py b/src/codex/types/projects/remediation_create_response.py index ad4e6893..9b8a8775 100644 --- a/src/codex/types/projects/remediation_create_response.py +++ b/src/codex/types/projects/remediation_create_response.py @@ -22,11 +22,13 @@ class RemediationCreateResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_edit_answer_response.py b/src/codex/types/projects/remediation_edit_answer_response.py index d8b34323..1d43c082 100644 --- a/src/codex/types/projects/remediation_edit_answer_response.py +++ b/src/codex/types/projects/remediation_edit_answer_response.py @@ -22,11 +22,13 @@ class RemediationEditAnswerResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_edit_draft_answer_response.py b/src/codex/types/projects/remediation_edit_draft_answer_response.py index 828035e8..80f80c07 100644 --- a/src/codex/types/projects/remediation_edit_draft_answer_response.py +++ b/src/codex/types/projects/remediation_edit_draft_answer_response.py @@ -22,11 +22,13 @@ class RemediationEditDraftAnswerResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py index 79997b09..9222eb9a 100644 --- a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py +++ b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py @@ -22,13 +22,15 @@ class RemediationGetResolvedLogsCountResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str resolved_logs_count: int - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 6181028d..4f9682b6 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -40,7 +40,6 @@ class QueryLog(BaseModel): """ is_bad_response: bool - """If an eval with should_escalate=True failed""" project_id: str @@ -60,6 +59,9 @@ class QueryLog(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + escalated: Optional[bool] = None + """If true, the question was escalated to Codex for an SME to review""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -72,6 +74,9 @@ class QueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrailed: Optional[bool] = None + """If true, the response was guardrailed""" + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/remediation_list_response.py b/src/codex/types/projects/remediation_list_response.py index 3e737970..9d5cda86 100644 --- a/src/codex/types/projects/remediation_list_response.py +++ b/src/codex/types/projects/remediation_list_response.py @@ -22,13 +22,15 @@ class Remediation(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str resolved_logs_count: int - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_pause_response.py b/src/codex/types/projects/remediation_pause_response.py index ae453581..97e1ac58 100644 --- a/src/codex/types/projects/remediation_pause_response.py +++ b/src/codex/types/projects/remediation_pause_response.py @@ -22,11 +22,13 @@ class RemediationPauseResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_publish_response.py b/src/codex/types/projects/remediation_publish_response.py index 5eb2c622..b43c8b98 100644 --- a/src/codex/types/projects/remediation_publish_response.py +++ b/src/codex/types/projects/remediation_publish_response.py @@ -22,11 +22,13 @@ class RemediationPublishResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_retrieve_response.py b/src/codex/types/projects/remediation_retrieve_response.py index 6fbd60d2..69a327e0 100644 --- a/src/codex/types/projects/remediation_retrieve_response.py +++ b/src/codex/types/projects/remediation_retrieve_response.py @@ -22,11 +22,13 @@ class RemediationRetrieveResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/src/codex/types/projects/remediation_unpause_response.py b/src/codex/types/projects/remediation_unpause_response.py index 789484cd..c3ce44fd 100644 --- a/src/codex/types/projects/remediation_unpause_response.py +++ b/src/codex/types/projects/remediation_unpause_response.py @@ -22,11 +22,13 @@ class RemediationUnpauseResponse(BaseModel): last_edited_by: Optional[str] = None + needs_review: bool + project_id: str question: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED"] + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] answer: Optional[str] = None diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py index 1fd4216f..22b83803 100644 --- a/tests/api_resources/projects/test_evals.py +++ b/tests/api_resources/projects/test_evals.py @@ -44,6 +44,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: query_identifier="query_identifier", response_identifier="response_identifier", should_escalate=True, + should_guardrail=True, threshold=0, threshold_direction="above", ) @@ -120,6 +121,7 @@ def test_method_update_with_all_params_overload_1(self, client: Codex) -> None: query_identifier="query_identifier", response_identifier="response_identifier", should_escalate=True, + should_guardrail=True, threshold=0, threshold_direction="above", ) @@ -200,6 +202,7 @@ def test_method_update_with_all_params_overload_2(self, client: Codex) -> None: enabled=True, priority=0, should_escalate=True, + should_guardrail=True, threshold=0, threshold_direction="above", ) @@ -378,6 +381,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> query_identifier="query_identifier", response_identifier="response_identifier", should_escalate=True, + should_guardrail=True, threshold=0, threshold_direction="above", ) @@ -454,6 +458,7 @@ async def test_method_update_with_all_params_overload_1(self, async_client: Asyn query_identifier="query_identifier", response_identifier="response_identifier", should_escalate=True, + should_guardrail=True, threshold=0, threshold_direction="above", ) @@ -534,6 +539,7 @@ async def test_method_update_with_all_params_overload_2(self, async_client: Asyn enabled=True, priority=0, should_escalate=True, + should_guardrail=True, threshold=0, threshold_direction="above", ) diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 6c95f273..e7d7eb1f 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -56,6 +56,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "query_identifier": "query_identifier", "response_identifier": "response_identifier", "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", } @@ -68,6 +69,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -77,6 +79,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -86,6 +89,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -95,6 +99,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -104,6 +109,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -226,6 +232,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "query_identifier": "query_identifier", "response_identifier": "response_identifier", "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", } @@ -238,6 +245,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -247,6 +255,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -256,6 +265,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -265,6 +275,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -274,6 +285,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -757,6 +769,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "query_identifier": "query_identifier", "response_identifier": "response_identifier", "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", } @@ -769,6 +782,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -778,6 +792,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -787,6 +802,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -796,6 +812,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -805,6 +822,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -927,6 +945,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "query_identifier": "query_identifier", "response_identifier": "response_identifier", "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", } @@ -939,6 +958,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -948,6 +968,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -957,6 +978,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -966,6 +988,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, @@ -975,6 +998,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "enabled": True, "priority": 0, "should_escalate": True, + "should_guardrail": True, "threshold": 0, "threshold_direction": "above", }, From fca40bf99465bfae4f61c65ec27f3e45f2dd96dd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:54:08 +0000 Subject: [PATCH 177/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7c31fce2..aa848759 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.21" + ".": "0.1.0-alpha.22" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d311e3f1..a2a3d7c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.21" +version = "0.1.0-alpha.22" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 3b23c98f..a88a1c39 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.21" # x-release-please-version +__version__ = "0.1.0-alpha.22" # x-release-please-version From f2fc581f478b5de321d4ae608705e432df7baba4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:17:37 +0000 Subject: [PATCH 178/320] feat(api): api update --- .stats.yml | 2 +- .../query_log_list_by_group_response.py | 33 +++++++++++++++- .../query_log_list_groups_response.py | 39 ++++++++++++++++++- .../types/projects/query_log_list_response.py | 39 ++++++++++++++++++- .../projects/query_log_retrieve_response.py | 38 +++++++++++++++++- ...remediation_list_resolved_logs_response.py | 39 ++++++++++++++++++- 6 files changed, 180 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index 04c13865..9e05e796 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 65 -openapi_spec_hash: 80696dc202de8bacc0e43506d7c210b0 +openapi_spec_hash: 8bc7a64933cd540d1d2499d055430832 config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index d11d8276..9d1e0e69 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -10,10 +10,31 @@ "QueryLogListByGroupResponse", "QueryLogsByGroup", "QueryLogsByGroupQueryLog", + "QueryLogsByGroupQueryLogFormattedEscalationEvalScores", + "QueryLogsByGroupQueryLogFormattedEvalScores", + "QueryLogsByGroupQueryLogFormattedGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", ] +class QueryLogsByGroupQueryLogFormattedEscalationEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogsByGroupQueryLogFormattedEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogsByGroupQueryLogFormattedGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + class QueryLogsByGroupQueryLogContext(BaseModel): content: str """The actual content/text of the document.""" @@ -36,7 +57,9 @@ class QueryLogsByGroupQueryLog(BaseModel): created_at: datetime - formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + formatted_escalation_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedEscalationEvalScores]] = None + + formatted_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedEvalScores]] = None """Format evaluation scores for frontend display with pass/fail status. Returns: Dictionary mapping eval keys to their formatted representation: { @@ -44,6 +67,8 @@ class QueryLogsByGroupQueryLog(BaseModel): eval_scores is None. """ + formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedGuardrailEvalScores]] = None + is_bad_response: bool project_id: str @@ -67,6 +92,9 @@ class QueryLogsByGroupQueryLog(BaseModel): escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" + escalation_evals: Optional[List[str]] = None + """Evals that should trigger escalation to SME""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -79,6 +107,9 @@ class QueryLogsByGroupQueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrail_evals: Optional[List[str]] = None + """Evals that should trigger guardrail""" + guardrailed: Optional[bool] = None """If true, the response was guardrailed""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index fd87c309..7b77cc05 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -6,7 +6,32 @@ from ..._models import BaseModel -__all__ = ["QueryLogListGroupsResponse", "QueryLogGroup", "QueryLogGroupContext"] +__all__ = [ + "QueryLogListGroupsResponse", + "QueryLogGroup", + "QueryLogGroupFormattedEscalationEvalScores", + "QueryLogGroupFormattedEvalScores", + "QueryLogGroupFormattedGuardrailEvalScores", + "QueryLogGroupContext", +] + + +class QueryLogGroupFormattedEscalationEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogGroupFormattedEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogGroupFormattedGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] class QueryLogGroupContext(BaseModel): @@ -31,7 +56,9 @@ class QueryLogGroup(BaseModel): created_at: datetime - formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + formatted_escalation_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEscalationEvalScores]] = None + + formatted_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEvalScores]] = None """Format evaluation scores for frontend display with pass/fail status. Returns: Dictionary mapping eval keys to their formatted representation: { @@ -39,6 +66,8 @@ class QueryLogGroup(BaseModel): eval_scores is None. """ + formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedGuardrailEvalScores]] = None + is_bad_response: bool needs_review: bool @@ -68,6 +97,9 @@ class QueryLogGroup(BaseModel): escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" + escalation_evals: Optional[List[str]] = None + """Evals that should trigger escalation to SME""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -80,6 +112,9 @@ class QueryLogGroup(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrail_evals: Optional[List[str]] = None + """Evals that should trigger guardrail""" + guardrailed: Optional[bool] = None """If true, the response was guardrailed""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index bfd37cdd..fa04904d 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -6,7 +6,32 @@ from ..._models import BaseModel -__all__ = ["QueryLogListResponse", "QueryLog", "QueryLogContext"] +__all__ = [ + "QueryLogListResponse", + "QueryLog", + "QueryLogFormattedEscalationEvalScores", + "QueryLogFormattedEvalScores", + "QueryLogFormattedGuardrailEvalScores", + "QueryLogContext", +] + + +class QueryLogFormattedEscalationEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogFormattedEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogFormattedGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] class QueryLogContext(BaseModel): @@ -31,7 +56,9 @@ class QueryLog(BaseModel): created_at: datetime - formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + formatted_escalation_eval_scores: Optional[Dict[str, QueryLogFormattedEscalationEvalScores]] = None + + formatted_eval_scores: Optional[Dict[str, QueryLogFormattedEvalScores]] = None """Format evaluation scores for frontend display with pass/fail status. Returns: Dictionary mapping eval keys to their formatted representation: { @@ -39,6 +66,8 @@ class QueryLog(BaseModel): eval_scores is None. """ + formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None + is_bad_response: bool project_id: str @@ -62,6 +91,9 @@ class QueryLog(BaseModel): escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" + escalation_evals: Optional[List[str]] = None + """Evals that should trigger escalation to SME""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -74,6 +106,9 @@ class QueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrail_evals: Optional[List[str]] = None + """Evals that should trigger guardrail""" + guardrailed: Optional[bool] = None """If true, the response was guardrailed""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 3b813eea..8bb61283 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -6,7 +6,31 @@ from ..._models import BaseModel -__all__ = ["QueryLogRetrieveResponse", "Context"] +__all__ = [ + "QueryLogRetrieveResponse", + "FormattedEscalationEvalScores", + "FormattedEvalScores", + "FormattedGuardrailEvalScores", + "Context", +] + + +class FormattedEscalationEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class FormattedEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class FormattedGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] class Context(BaseModel): @@ -31,7 +55,9 @@ class QueryLogRetrieveResponse(BaseModel): created_at: datetime - formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None + + formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None """Format evaluation scores for frontend display with pass/fail status. Returns: Dictionary mapping eval keys to their formatted representation: { @@ -39,6 +65,8 @@ class QueryLogRetrieveResponse(BaseModel): eval_scores is None. """ + formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None + is_bad_response: bool project_id: str @@ -62,6 +90,9 @@ class QueryLogRetrieveResponse(BaseModel): escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" + escalation_evals: Optional[List[str]] = None + """Evals that should trigger escalation to SME""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -74,6 +105,9 @@ class QueryLogRetrieveResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrail_evals: Optional[List[str]] = None + """Evals that should trigger guardrail""" + guardrailed: Optional[bool] = None """If true, the response was guardrailed""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 4f9682b6..b2315aa4 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -6,7 +6,32 @@ from ..._models import BaseModel -__all__ = ["RemediationListResolvedLogsResponse", "QueryLog", "QueryLogContext"] +__all__ = [ + "RemediationListResolvedLogsResponse", + "QueryLog", + "QueryLogFormattedEscalationEvalScores", + "QueryLogFormattedEvalScores", + "QueryLogFormattedGuardrailEvalScores", + "QueryLogContext", +] + + +class QueryLogFormattedEscalationEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogFormattedEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + +class QueryLogFormattedGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] class QueryLogContext(BaseModel): @@ -31,7 +56,9 @@ class QueryLog(BaseModel): created_at: datetime - formatted_eval_scores: Optional[Dict[str, Dict[str, Union[float, Literal["pass", "fail"]]]]] = None + formatted_escalation_eval_scores: Optional[Dict[str, QueryLogFormattedEscalationEvalScores]] = None + + formatted_eval_scores: Optional[Dict[str, QueryLogFormattedEvalScores]] = None """Format evaluation scores for frontend display with pass/fail status. Returns: Dictionary mapping eval keys to their formatted representation: { @@ -39,6 +66,8 @@ class QueryLog(BaseModel): eval_scores is None. """ + formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None + is_bad_response: bool project_id: str @@ -62,6 +91,9 @@ class QueryLog(BaseModel): escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" + escalation_evals: Optional[List[str]] = None + """Evals that should trigger escalation to SME""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -74,6 +106,9 @@ class QueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + guardrail_evals: Optional[List[str]] = None + """Evals that should trigger guardrail""" + guardrailed: Optional[bool] = None """If true, the response was guardrailed""" From 3032709fc4eee6a1c92228ec502a52053a902b94 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 00:17:35 +0000 Subject: [PATCH 179/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 20 ++++++++++---------- src/codex/types/project_update_params.py | 8 ++++---- tests/api_resources/test_projects.py | 24 ++++-------------------- 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9e05e796..31393e04 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 65 -openapi_spec_hash: 8bc7a64933cd540d1d2499d055430832 +openapi_spec_hash: 5d686da2afda75185dc9e4190a42b75c config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 22b5caff..fc5a43a4 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -212,10 +212,10 @@ def update( self, project_id: str, *, - config: project_update_params.Config, - name: str, - auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, + auto_clustering_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + config: Optional[project_update_params.Config] | NotGiven = NOT_GIVEN, description: Optional[str] | NotGiven = NOT_GIVEN, + name: Optional[str] | NotGiven = NOT_GIVEN, # 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, @@ -241,10 +241,10 @@ def update( f"/api/projects/{project_id}", body=maybe_transform( { - "config": config, - "name": name, "auto_clustering_enabled": auto_clustering_enabled, + "config": config, "description": description, + "name": name, }, project_update_params.ProjectUpdateParams, ), @@ -820,10 +820,10 @@ async def update( self, project_id: str, *, - config: project_update_params.Config, - name: str, - auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, + auto_clustering_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + config: Optional[project_update_params.Config] | NotGiven = NOT_GIVEN, description: Optional[str] | NotGiven = NOT_GIVEN, + name: Optional[str] | NotGiven = NOT_GIVEN, # 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, @@ -849,10 +849,10 @@ async def update( f"/api/projects/{project_id}", body=await async_maybe_transform( { - "config": config, - "name": name, "auto_clustering_enabled": auto_clustering_enabled, + "config": config, "description": description, + "name": name, }, project_update_params.ProjectUpdateParams, ), diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 73dad672..3e244411 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -21,14 +21,14 @@ class ProjectUpdateParams(TypedDict, total=False): - config: Required[Config] + auto_clustering_enabled: Optional[bool] - name: Required[str] - - auto_clustering_enabled: bool + config: Optional[Config] description: Optional[str] + name: Optional[str] + class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): criteria: Required[str] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index e7d7eb1f..0764d9a1 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -206,8 +206,6 @@ def test_path_params_retrieve(self, client: Codex) -> None: def test_method_update(self, client: Codex) -> None: project = client.projects.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={}, - name="name", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -216,6 +214,7 @@ def test_method_update(self, client: Codex) -> None: def test_method_update_with_all_params(self, client: Codex) -> None: project = client.projects.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auto_clustering_enabled=True, config={ "clustering_use_llm_matching": True, "eval_config": { @@ -298,9 +297,8 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "query_use_llm_matching": True, "upper_llm_match_distance_threshold": 0, }, - name="name", - auto_clustering_enabled=True, description="description", + name="name", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -309,8 +307,6 @@ def test_method_update_with_all_params(self, client: Codex) -> None: def test_raw_response_update(self, client: Codex) -> None: response = client.projects.with_raw_response.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={}, - name="name", ) assert response.is_closed is True @@ -323,8 +319,6 @@ def test_raw_response_update(self, client: Codex) -> None: def test_streaming_response_update(self, client: Codex) -> None: with client.projects.with_streaming_response.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={}, - name="name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -340,8 +334,6 @@ def test_path_params_update(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): client.projects.with_raw_response.update( project_id="", - config={}, - name="name", ) @pytest.mark.skip() @@ -919,8 +911,6 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: async def test_method_update(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={}, - name="name", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -929,6 +919,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auto_clustering_enabled=True, config={ "clustering_use_llm_matching": True, "eval_config": { @@ -1011,9 +1002,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "query_use_llm_matching": True, "upper_llm_match_distance_threshold": 0, }, - name="name", - auto_clustering_enabled=True, description="description", + name="name", ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) @@ -1022,8 +1012,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={}, - name="name", ) assert response.is_closed is True @@ -1036,8 +1024,6 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.update( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - config={}, - name="name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1053,8 +1039,6 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): await async_client.projects.with_raw_response.update( project_id="", - config={}, - name="name", ) @pytest.mark.skip() From c205ac5b02a931b80cbd609f3923fc6d128f5f86 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:17:43 +0000 Subject: [PATCH 180/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 31393e04..3ff2f634 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 65 -openapi_spec_hash: 5d686da2afda75185dc9e4190a42b75c +openapi_spec_hash: 94969e7925542f0cc845f6e3d299ed3c config_hash: 14b2643a0ec60cf326dfed00939644ff From 026828e8b41d66d9b65e9fa5b061c9b36ac15b1b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 02:29:21 +0000 Subject: [PATCH 181/320] =?UTF-8?q?fix(ci):=20release-doctor=20=E2=80=94?= =?UTF-8?q?=20report=20correct=20token=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/check-release-environment | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/check-release-environment b/bin/check-release-environment index a1446a75..b845b0f4 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The CODEX_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[@]} From 3c9c4aedc9457891385a7321451762aa9111df48 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 08:28:28 +0000 Subject: [PATCH 182/320] chore(ci): only run for pushes and fork pull requests --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f7778ae..12f686e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/codex-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -42,6 +43,7 @@ jobs: contents: read id-token: write runs-on: depot-ubuntu-24.04 + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From 3ae3c670f5f9c5e39b2746c5e51de59de8a224c8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 02:25:48 +0000 Subject: [PATCH 183/320] fix(ci): correct conditional --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12f686e5..bbb722b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,14 +36,13 @@ jobs: run: ./scripts/lint upload: - if: github.repository == 'stainless-sdks/codex-python' + if: github.repository == 'stainless-sdks/codex-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 name: upload permissions: contents: read id-token: write runs-on: depot-ubuntu-24.04 - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From 6a38d107b30cbe5ae08146ea04c447fc204f962a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:17:38 +0000 Subject: [PATCH 184/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 88 +++++++++++++++++++ .../query_log_list_by_group_params.py | 12 +++ .../projects/query_log_list_groups_params.py | 12 +++ .../types/projects/query_log_list_params.py | 9 ++ .../api_resources/projects/test_query_logs.py | 22 +++++ 6 files changed, 144 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 3ff2f634..c509062f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 65 -openapi_spec_hash: 94969e7925542f0cc845f6e3d299ed3c +openapi_spec_hash: f63d4542b4bd1530ced013eb686cab99 config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 32ec7390..e97243ec 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -92,9 +92,12 @@ def list( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] @@ -118,6 +121,12 @@ def list( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + failed_evals: Filter by evals that failed + + guardrailed: Filter by guardrailed status + + passed_evals: Filter by evals that passed + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) was_cache_hit: Filter by cache hit status @@ -144,9 +153,12 @@ def list( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "failed_evals": failed_evals, + "guardrailed": guardrailed, "limit": limit, "offset": offset, "order": order, + "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, "was_cache_hit": was_cache_hit, @@ -164,9 +176,13 @@ def list_by_group( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, + needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] @@ -191,6 +207,14 @@ def list_by_group( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + failed_evals: Filter by evals that failed + + guardrailed: Filter by guardrailed status + + needs_review: Filter logs that need review + + passed_evals: Filter by evals that passed + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) remediation_ids: List of groups to list child logs for @@ -219,9 +243,13 @@ def list_by_group( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "failed_evals": failed_evals, + "guardrailed": guardrailed, "limit": limit, + "needs_review": needs_review, "offset": offset, "order": order, + "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "remediation_ids": remediation_ids, "sort": sort, @@ -240,9 +268,13 @@ def list_groups( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, + needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] @@ -267,6 +299,14 @@ def list_groups( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + failed_evals: Filter by evals that failed + + guardrailed: Filter by guardrailed status + + needs_review: Filter log groups that need review + + passed_evals: Filter by evals that passed + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) was_cache_hit: Filter by cache hit status @@ -293,9 +333,13 @@ def list_groups( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "failed_evals": failed_evals, + "guardrailed": guardrailed, "limit": limit, + "needs_review": needs_review, "offset": offset, "order": order, + "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, "was_cache_hit": was_cache_hit, @@ -406,9 +450,12 @@ async def list( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] @@ -432,6 +479,12 @@ async def list( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + failed_evals: Filter by evals that failed + + guardrailed: Filter by guardrailed status + + passed_evals: Filter by evals that passed + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) was_cache_hit: Filter by cache hit status @@ -458,9 +511,12 @@ async def list( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "failed_evals": failed_evals, + "guardrailed": guardrailed, "limit": limit, "offset": offset, "order": order, + "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, "was_cache_hit": was_cache_hit, @@ -478,9 +534,13 @@ async def list_by_group( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, + needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] @@ -505,6 +565,14 @@ async def list_by_group( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + failed_evals: Filter by evals that failed + + guardrailed: Filter by guardrailed status + + needs_review: Filter logs that need review + + passed_evals: Filter by evals that passed + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) remediation_ids: List of groups to list child logs for @@ -533,9 +601,13 @@ async def list_by_group( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "failed_evals": failed_evals, + "guardrailed": guardrailed, "limit": limit, + "needs_review": needs_review, "offset": offset, "order": order, + "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "remediation_ids": remediation_ids, "sort": sort, @@ -554,9 +626,13 @@ async def list_groups( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, + needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] @@ -581,6 +657,14 @@ async def list_groups( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + failed_evals: Filter by evals that failed + + guardrailed: Filter by guardrailed status + + needs_review: Filter log groups that need review + + passed_evals: Filter by evals that passed + primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) was_cache_hit: Filter by cache hit status @@ -607,9 +691,13 @@ async def list_groups( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "failed_evals": failed_evals, + "guardrailed": guardrailed, "limit": limit, + "needs_review": needs_review, "offset": offset, "order": order, + "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, "was_cache_hit": was_cache_hit, diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 66166a19..b44970a1 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -21,12 +21,24 @@ class QueryLogListByGroupParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + failed_evals: Optional[List[str]] + """Filter by evals that failed""" + + guardrailed: Optional[bool] + """Filter by guardrailed status""" + limit: int + needs_review: Optional[bool] + """Filter logs that need review""" + offset: int order: Literal["asc", "desc"] + passed_evals: Optional[List[str]] + """Filter by evals that passed""" + primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index 558ac0b2..94d549f5 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -21,12 +21,24 @@ class QueryLogListGroupsParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + failed_evals: Optional[List[str]] + """Filter by evals that failed""" + + guardrailed: Optional[bool] + """Filter by guardrailed status""" + limit: int + needs_review: Optional[bool] + """Filter log groups that need review""" + offset: int order: Literal["asc", "desc"] + passed_evals: Optional[List[str]] + """Filter by evals that passed""" + primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 9cf3211f..0f72b241 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -21,12 +21,21 @@ class QueryLogListParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + failed_evals: Optional[List[str]] + """Filter by evals that failed""" + + guardrailed: Optional[bool] + """Filter by guardrailed status""" + limit: int offset: int order: Literal["asc", "desc"] + passed_evals: Optional[List[str]] + """Filter by evals that passed""" + primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] ] diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index d75dcabe..68d78ced 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -92,9 +92,12 @@ def test_method_list_with_all_params(self, client: Codex) -> None: created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + failed_evals=["string"], + guardrailed=True, limit=1, offset=0, order="asc", + passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", was_cache_hit=True, @@ -151,9 +154,13 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + failed_evals=["string"], + guardrailed=True, limit=1, + needs_review=True, offset=0, order="asc", + passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], sort="created_at", @@ -211,9 +218,13 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + failed_evals=["string"], + guardrailed=True, limit=1, + needs_review=True, offset=0, order="asc", + passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", was_cache_hit=True, @@ -380,9 +391,12 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + failed_evals=["string"], + guardrailed=True, limit=1, offset=0, order="asc", + passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", was_cache_hit=True, @@ -439,9 +453,13 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + failed_evals=["string"], + guardrailed=True, limit=1, + needs_review=True, offset=0, order="asc", + passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], sort="created_at", @@ -499,9 +517,13 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + failed_evals=["string"], + guardrailed=True, limit=1, + needs_review=True, offset=0, order="asc", + passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", was_cache_hit=True, From bed966ef781b12126f87bab442fc546087ae72a0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:17:29 +0000 Subject: [PATCH 185/320] feat(api): api update --- .stats.yml | 4 +- README.md | 69 -- api.md | 32 - src/codex/resources/projects/__init__.py | 28 - src/codex/resources/projects/clusters.py | 332 ------ src/codex/resources/projects/entries.py | 948 --------------- src/codex/resources/projects/projects.py | 64 -- src/codex/types/projects/__init__.py | 10 - .../types/projects/cluster_list_params.py | 34 - .../types/projects/cluster_list_response.py | 214 ---- .../cluster_list_variants_response.py | 14 - src/codex/types/projects/entry.py | 210 ---- .../types/projects/entry_create_params.py | 28 - .../types/projects/entry_notify_sme_params.py | 30 - .../projects/entry_notify_sme_response.py | 13 - .../types/projects/entry_query_params.py | 300 ----- .../types/projects/entry_query_response.py | 194 ---- .../types/projects/entry_update_params.py | 18 - .../query_log_list_by_group_response.py | 4 + .../query_log_list_groups_response.py | 2 +- .../projects/query_log_retrieve_response.py | 4 + tests/api_resources/projects/test_clusters.py | 247 ---- tests/api_resources/projects/test_entries.py | 1014 ----------------- 23 files changed, 11 insertions(+), 3802 deletions(-) delete mode 100644 src/codex/resources/projects/clusters.py delete mode 100644 src/codex/resources/projects/entries.py delete mode 100644 src/codex/types/projects/cluster_list_params.py delete mode 100644 src/codex/types/projects/cluster_list_response.py delete mode 100644 src/codex/types/projects/cluster_list_variants_response.py delete mode 100644 src/codex/types/projects/entry.py delete mode 100644 src/codex/types/projects/entry_create_params.py delete mode 100644 src/codex/types/projects/entry_notify_sme_params.py delete mode 100644 src/codex/types/projects/entry_notify_sme_response.py delete mode 100644 src/codex/types/projects/entry_query_params.py delete mode 100644 src/codex/types/projects/entry_query_response.py delete mode 100644 src/codex/types/projects/entry_update_params.py delete mode 100644 tests/api_resources/projects/test_clusters.py delete mode 100644 tests/api_resources/projects/test_entries.py diff --git a/.stats.yml b/.stats.yml index c509062f..cc72a8a8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 65 -openapi_spec_hash: f63d4542b4bd1530ced013eb686cab99 +configured_endpoints: 55 +openapi_spec_hash: b2b026661b19d060e5eac490807fe445 config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/README.md b/README.md index 74f841bf..756f6d69 100644 --- a/README.md +++ b/README.md @@ -113,75 +113,6 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. -## Pagination - -List methods in the Codex API are paginated. - -This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually: - -```python -from codex import Codex - -client = Codex() - -all_clusters = [] -# Automatically fetches more pages as needed. -for cluster in client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", -): - # Do something with cluster here - all_clusters.append(cluster) -print(all_clusters) -``` - -Or, asynchronously: - -```python -import asyncio -from codex import AsyncCodex - -client = AsyncCodex() - - -async def main() -> None: - all_clusters = [] - # Iterate through items across all pages, issuing requests as needed. - async for cluster in client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ): - all_clusters.append(cluster) - print(all_clusters) - - -asyncio.run(main()) -``` - -Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: - -```python -first_page = await client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", -) -if first_page.has_next_page(): - print(f"will fetch next page using these details: {first_page.next_page_info()}") - next_page = await first_page.get_next_page() - print(f"number of items we just fetched: {len(next_page.clusters)}") - -# Remove `await` for non-async usage. -``` - -Or just work directly with the returned data: - -```python -first_page = await client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", -) -for cluster in first_page.clusters: - print(cluster.id) - -# Remove `await` for non-async usage. -``` - ## Nested params Nested parameters are dictionaries, typed using `TypedDict`, for example: diff --git a/api.md b/api.md index 13e82147..ba4dfd63 100644 --- a/api.md +++ b/api.md @@ -180,38 +180,6 @@ Methods: - client.projects.access_keys.retrieve_project_id() -> AccessKeyRetrieveProjectIDResponse - client.projects.access_keys.revoke(access_key_id, \*, project_id) -> None -## Entries - -Types: - -```python -from codex.types.projects import Entry, EntryNotifySmeResponse, EntryQueryResponse -``` - -Methods: - -- client.projects.entries.create(project_id, \*\*params) -> Entry -- client.projects.entries.retrieve(entry_id, \*, project_id) -> Entry -- client.projects.entries.update(entry_id, \*, project_id, \*\*params) -> Entry -- client.projects.entries.delete(entry_id, \*, project_id) -> None -- client.projects.entries.notify_sme(entry_id, \*, project_id, \*\*params) -> EntryNotifySmeResponse -- client.projects.entries.publish_draft_answer(entry_id, \*, project_id) -> Entry -- client.projects.entries.query(project_id, \*\*params) -> EntryQueryResponse -- client.projects.entries.unpublish_answer(entry_id, \*, project_id) -> Entry - -## Clusters - -Types: - -```python -from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse -``` - -Methods: - -- client.projects.clusters.list(project_id, \*\*params) -> SyncOffsetPageClusters[ClusterListResponse] -- client.projects.clusters.list_variants(representative_entry_id, \*, project_id) -> ClusterListVariantsResponse - ## Evals Types: diff --git a/src/codex/resources/projects/__init__.py b/src/codex/resources/projects/__init__.py index 178855ac..c1627d29 100644 --- a/src/codex/resources/projects/__init__.py +++ b/src/codex/resources/projects/__init__.py @@ -8,22 +8,6 @@ EvalsResourceWithStreamingResponse, AsyncEvalsResourceWithStreamingResponse, ) -from .entries import ( - EntriesResource, - AsyncEntriesResource, - EntriesResourceWithRawResponse, - AsyncEntriesResourceWithRawResponse, - EntriesResourceWithStreamingResponse, - AsyncEntriesResourceWithStreamingResponse, -) -from .clusters import ( - ClustersResource, - AsyncClustersResource, - ClustersResourceWithRawResponse, - AsyncClustersResourceWithRawResponse, - ClustersResourceWithStreamingResponse, - AsyncClustersResourceWithStreamingResponse, -) from .projects import ( ProjectsResource, AsyncProjectsResource, @@ -64,18 +48,6 @@ "AsyncAccessKeysResourceWithRawResponse", "AccessKeysResourceWithStreamingResponse", "AsyncAccessKeysResourceWithStreamingResponse", - "EntriesResource", - "AsyncEntriesResource", - "EntriesResourceWithRawResponse", - "AsyncEntriesResourceWithRawResponse", - "EntriesResourceWithStreamingResponse", - "AsyncEntriesResourceWithStreamingResponse", - "ClustersResource", - "AsyncClustersResource", - "ClustersResourceWithRawResponse", - "AsyncClustersResourceWithRawResponse", - "ClustersResourceWithStreamingResponse", - "AsyncClustersResourceWithStreamingResponse", "EvalsResource", "AsyncEvalsResource", "EvalsResourceWithRawResponse", diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py deleted file mode 100644 index 97124642..00000000 --- a/src/codex/resources/projects/clusters.py +++ /dev/null @@ -1,332 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Optional -from typing_extensions import Literal - -import httpx - -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ...pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters -from ..._base_client import AsyncPaginator, make_request_options -from ...types.projects import cluster_list_params -from ...types.projects.cluster_list_response import ClusterListResponse -from ...types.projects.cluster_list_variants_response import ClusterListVariantsResponse - -__all__ = ["ClustersResource", "AsyncClustersResource"] - - -class ClustersResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ClustersResourceWithRawResponse: - """ - 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return ClustersResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ClustersResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return ClustersResourceWithStreamingResponse(self) - - def list( - self, - project_id: str, - *, - eval_issue_types: List[ - Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"] - ] - | NotGiven = NOT_GIVEN, - instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[ - Literal[ - "created_at", - "answered_at", - "cluster_frequency_count", - "custom_rank", - "eval_score", - "html_format_score", - "content_structure_score", - ] - ] - | NotGiven = NOT_GIVEN, - states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, - # 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, - ) -> SyncOffsetPageClusters[ClusterListResponse]: - """ - List knowledge entries for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._get_api_list( - f"/api/projects/{project_id}/entries/clusters", - page=SyncOffsetPageClusters[ClusterListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "eval_issue_types": eval_issue_types, - "instruction_adherence_failure": instruction_adherence_failure, - "limit": limit, - "offset": offset, - "order": order, - "sort": sort, - "states": states, - }, - cluster_list_params.ClusterListParams, - ), - ), - model=ClusterListResponse, - ) - - def list_variants( - self, - representative_entry_id: str, - *, - project_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, - ) -> ClusterListVariantsResponse: - """ - Get Cluster Variants Route - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not representative_entry_id: - raise ValueError( - f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}" - ) - return self._get( - f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ClusterListVariantsResponse, - ) - - -class AsyncClustersResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncClustersResourceWithRawResponse: - """ - 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return AsyncClustersResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncClustersResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return AsyncClustersResourceWithStreamingResponse(self) - - def list( - self, - project_id: str, - *, - eval_issue_types: List[ - Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"] - ] - | NotGiven = NOT_GIVEN, - instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[ - Literal[ - "created_at", - "answered_at", - "cluster_frequency_count", - "custom_rank", - "eval_score", - "html_format_score", - "content_structure_score", - ] - ] - | NotGiven = NOT_GIVEN, - states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[ClusterListResponse, AsyncOffsetPageClusters[ClusterListResponse]]: - """ - List knowledge entries for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._get_api_list( - f"/api/projects/{project_id}/entries/clusters", - page=AsyncOffsetPageClusters[ClusterListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "eval_issue_types": eval_issue_types, - "instruction_adherence_failure": instruction_adherence_failure, - "limit": limit, - "offset": offset, - "order": order, - "sort": sort, - "states": states, - }, - cluster_list_params.ClusterListParams, - ), - ), - model=ClusterListResponse, - ) - - async def list_variants( - self, - representative_entry_id: str, - *, - project_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, - ) -> ClusterListVariantsResponse: - """ - Get Cluster Variants Route - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not representative_entry_id: - raise ValueError( - f"Expected a non-empty value for `representative_entry_id` but received {representative_entry_id!r}" - ) - return await self._get( - f"/api/projects/{project_id}/entries/clusters/{representative_entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ClusterListVariantsResponse, - ) - - -class ClustersResourceWithRawResponse: - def __init__(self, clusters: ClustersResource) -> None: - self._clusters = clusters - - self.list = to_raw_response_wrapper( - clusters.list, - ) - self.list_variants = to_raw_response_wrapper( - clusters.list_variants, - ) - - -class AsyncClustersResourceWithRawResponse: - def __init__(self, clusters: AsyncClustersResource) -> None: - self._clusters = clusters - - self.list = async_to_raw_response_wrapper( - clusters.list, - ) - self.list_variants = async_to_raw_response_wrapper( - clusters.list_variants, - ) - - -class ClustersResourceWithStreamingResponse: - def __init__(self, clusters: ClustersResource) -> None: - self._clusters = clusters - - self.list = to_streamed_response_wrapper( - clusters.list, - ) - self.list_variants = to_streamed_response_wrapper( - clusters.list_variants, - ) - - -class AsyncClustersResourceWithStreamingResponse: - def __init__(self, clusters: AsyncClustersResource) -> None: - self._clusters = clusters - - self.list = async_to_streamed_response_wrapper( - clusters.list, - ) - self.list_variants = async_to_streamed_response_wrapper( - clusters.list_variants, - ) diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py deleted file mode 100644 index c6b43a4a..00000000 --- a/src/codex/resources/projects/entries.py +++ /dev/null @@ -1,948 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import typing_extensions -from typing import Iterable, Optional - -import httpx - -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import maybe_transform, strip_not_given, 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.projects import entry_query_params, entry_create_params, entry_update_params, entry_notify_sme_params -from ...types.projects.entry import Entry -from ...types.projects.entry_query_response import EntryQueryResponse -from ...types.projects.entry_notify_sme_response import EntryNotifySmeResponse - -__all__ = ["EntriesResource", "AsyncEntriesResource"] - - -class EntriesResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> EntriesResourceWithRawResponse: - """ - 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return EntriesResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> EntriesResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return EntriesResourceWithStreamingResponse(self) - - def create( - self, - project_id: str, - *, - question: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, - draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Create a new knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - extra_headers = { - **strip_not_given( - { - "x-client-library-version": x_client_library_version, - "x-integration-type": x_integration_type, - "x-source": x_source, - "x-stainless-package-version": x_stainless_package_version, - } - ), - **(extra_headers or {}), - } - return self._post( - f"/api/projects/{project_id}/entries/", - body=maybe_transform( - { - "question": question, - "answer": answer, - "client_query_metadata": client_query_metadata, - "draft_answer": draft_answer, - }, - entry_create_params.EntryCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - def retrieve( - self, - entry_id: str, - *, - project_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, - ) -> Entry: - """ - Get a knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return self._get( - f"/api/projects/{project_id}/entries/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - def update( - self, - entry_id: str, - *, - project_id: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - question: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Update a knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return self._put( - f"/api/projects/{project_id}/entries/{entry_id}", - body=maybe_transform( - { - "answer": answer, - "draft_answer": draft_answer, - "question": question, - }, - entry_update_params.EntryUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - def delete( - self, - entry_id: str, - *, - project_id: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> None: - """ - Delete a knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._delete( - f"/api/projects/{project_id}/entries/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - def notify_sme( - self, - entry_id: str, - *, - project_id: str, - email: str, - view_context: entry_notify_sme_params.ViewContext, - # 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, - ) -> EntryNotifySmeResponse: - """ - Notify a subject matter expert to review and answer a specific entry. - - Returns: SMENotificationResponse with status and notification details - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return self._post( - f"/api/projects/{project_id}/entries/{entry_id}/notifications", - body=maybe_transform( - { - "email": email, - "view_context": view_context, - }, - entry_notify_sme_params.EntryNotifySmeParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=EntryNotifySmeResponse, - ) - - def publish_draft_answer( - self, - entry_id: str, - *, - project_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, - ) -> Entry: - """Promote a draft answer to a published answer for a knowledge entry. - - This always - results in the entry's draft answer being removed. If the entry already has a - published answer, it will be overwritten and permanently lost. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return self._put( - f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - @typing_extensions.deprecated("deprecated") - def query( - self, - project_id: str, - *, - question: str, - use_llm_matching: bool | NotGiven = NOT_GIVEN, - client_metadata: Optional[object] | NotGiven = NOT_GIVEN, - query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> EntryQueryResponse: - """ - Query Entries Route - - Args: - client_metadata: Deprecated: Use query_metadata instead - - query_metadata: Optional logging data that can be provided by the client. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - extra_headers = { - **strip_not_given( - { - "x-client-library-version": x_client_library_version, - "x-integration-type": x_integration_type, - "x-source": x_source, - "x-stainless-package-version": x_stainless_package_version, - } - ), - **(extra_headers or {}), - } - return self._post( - f"/api/projects/{project_id}/entries/query", - body=maybe_transform( - { - "question": question, - "client_metadata": client_metadata, - "query_metadata": query_metadata, - }, - entry_query_params.EntryQueryParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams), - ), - cast_to=EntryQueryResponse, - ) - - def unpublish_answer( - self, - entry_id: str, - *, - project_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, - ) -> Entry: - """Unpublish an answer for a knowledge entry. - - This always results in the entry's - answer being removed. If the entry does not already have a draft answer, the - current answer will be retained as the draft answer. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return self._put( - f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - -class AsyncEntriesResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncEntriesResourceWithRawResponse: - """ - 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return AsyncEntriesResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncEntriesResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return AsyncEntriesResourceWithStreamingResponse(self) - - async def create( - self, - project_id: str, - *, - question: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, - draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Create a new knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - extra_headers = { - **strip_not_given( - { - "x-client-library-version": x_client_library_version, - "x-integration-type": x_integration_type, - "x-source": x_source, - "x-stainless-package-version": x_stainless_package_version, - } - ), - **(extra_headers or {}), - } - return await self._post( - f"/api/projects/{project_id}/entries/", - body=await async_maybe_transform( - { - "question": question, - "answer": answer, - "client_query_metadata": client_query_metadata, - "draft_answer": draft_answer, - }, - entry_create_params.EntryCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - async def retrieve( - self, - entry_id: str, - *, - project_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, - ) -> Entry: - """ - Get a knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return await self._get( - f"/api/projects/{project_id}/entries/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - async def update( - self, - entry_id: str, - *, - project_id: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - question: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> Entry: - """ - Update a knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return await self._put( - f"/api/projects/{project_id}/entries/{entry_id}", - body=await async_maybe_transform( - { - "answer": answer, - "draft_answer": draft_answer, - "question": question, - }, - entry_update_params.EntryUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - async def delete( - self, - entry_id: str, - *, - project_id: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> None: - """ - Delete a knowledge entry for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return await self._delete( - f"/api/projects/{project_id}/entries/{entry_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - async def notify_sme( - self, - entry_id: str, - *, - project_id: str, - email: str, - view_context: entry_notify_sme_params.ViewContext, - # 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, - ) -> EntryNotifySmeResponse: - """ - Notify a subject matter expert to review and answer a specific entry. - - Returns: SMENotificationResponse with status and notification details - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return await self._post( - f"/api/projects/{project_id}/entries/{entry_id}/notifications", - body=await async_maybe_transform( - { - "email": email, - "view_context": view_context, - }, - entry_notify_sme_params.EntryNotifySmeParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=EntryNotifySmeResponse, - ) - - async def publish_draft_answer( - self, - entry_id: str, - *, - project_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, - ) -> Entry: - """Promote a draft answer to a published answer for a knowledge entry. - - This always - results in the entry's draft answer being removed. If the entry already has a - published answer, it will be overwritten and permanently lost. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return await self._put( - f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - @typing_extensions.deprecated("deprecated") - async def query( - self, - project_id: str, - *, - question: str, - use_llm_matching: bool | NotGiven = NOT_GIVEN, - client_metadata: Optional[object] | NotGiven = NOT_GIVEN, - query_metadata: Optional[entry_query_params.QueryMetadata] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> EntryQueryResponse: - """ - Query Entries Route - - Args: - client_metadata: Deprecated: Use query_metadata instead - - query_metadata: Optional logging data that can be provided by the client. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - extra_headers = { - **strip_not_given( - { - "x-client-library-version": x_client_library_version, - "x-integration-type": x_integration_type, - "x-source": x_source, - "x-stainless-package-version": x_stainless_package_version, - } - ), - **(extra_headers or {}), - } - return await self._post( - f"/api/projects/{project_id}/entries/query", - body=await async_maybe_transform( - { - "question": question, - "client_metadata": client_metadata, - "query_metadata": query_metadata, - }, - entry_query_params.EntryQueryParams, - ), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams - ), - ), - cast_to=EntryQueryResponse, - ) - - async def unpublish_answer( - self, - entry_id: str, - *, - project_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, - ) -> Entry: - """Unpublish an answer for a knowledge entry. - - This always results in the entry's - answer being removed. If the entry does not already have a draft answer, the - current answer will be retained as the draft answer. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not entry_id: - raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") - return await self._put( - f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Entry, - ) - - -class EntriesResourceWithRawResponse: - def __init__(self, entries: EntriesResource) -> None: - self._entries = entries - - self.create = to_raw_response_wrapper( - entries.create, - ) - self.retrieve = to_raw_response_wrapper( - entries.retrieve, - ) - self.update = to_raw_response_wrapper( - entries.update, - ) - self.delete = to_raw_response_wrapper( - entries.delete, - ) - self.notify_sme = to_raw_response_wrapper( - entries.notify_sme, - ) - self.publish_draft_answer = to_raw_response_wrapper( - entries.publish_draft_answer, - ) - self.query = ( # pyright: ignore[reportDeprecated] - to_raw_response_wrapper( - entries.query # pyright: ignore[reportDeprecated], - ) - ) - self.unpublish_answer = to_raw_response_wrapper( - entries.unpublish_answer, - ) - - -class AsyncEntriesResourceWithRawResponse: - def __init__(self, entries: AsyncEntriesResource) -> None: - self._entries = entries - - self.create = async_to_raw_response_wrapper( - entries.create, - ) - self.retrieve = async_to_raw_response_wrapper( - entries.retrieve, - ) - self.update = async_to_raw_response_wrapper( - entries.update, - ) - self.delete = async_to_raw_response_wrapper( - entries.delete, - ) - self.notify_sme = async_to_raw_response_wrapper( - entries.notify_sme, - ) - self.publish_draft_answer = async_to_raw_response_wrapper( - entries.publish_draft_answer, - ) - self.query = ( # pyright: ignore[reportDeprecated] - async_to_raw_response_wrapper( - entries.query # pyright: ignore[reportDeprecated], - ) - ) - self.unpublish_answer = async_to_raw_response_wrapper( - entries.unpublish_answer, - ) - - -class EntriesResourceWithStreamingResponse: - def __init__(self, entries: EntriesResource) -> None: - self._entries = entries - - self.create = to_streamed_response_wrapper( - entries.create, - ) - self.retrieve = to_streamed_response_wrapper( - entries.retrieve, - ) - self.update = to_streamed_response_wrapper( - entries.update, - ) - self.delete = to_streamed_response_wrapper( - entries.delete, - ) - self.notify_sme = to_streamed_response_wrapper( - entries.notify_sme, - ) - self.publish_draft_answer = to_streamed_response_wrapper( - entries.publish_draft_answer, - ) - self.query = ( # pyright: ignore[reportDeprecated] - to_streamed_response_wrapper( - entries.query # pyright: ignore[reportDeprecated], - ) - ) - self.unpublish_answer = to_streamed_response_wrapper( - entries.unpublish_answer, - ) - - -class AsyncEntriesResourceWithStreamingResponse: - def __init__(self, entries: AsyncEntriesResource) -> None: - self._entries = entries - - self.create = async_to_streamed_response_wrapper( - entries.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - entries.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - entries.update, - ) - self.delete = async_to_streamed_response_wrapper( - entries.delete, - ) - self.notify_sme = async_to_streamed_response_wrapper( - entries.notify_sme, - ) - self.publish_draft_answer = async_to_streamed_response_wrapper( - entries.publish_draft_answer, - ) - self.query = ( # pyright: ignore[reportDeprecated] - async_to_streamed_response_wrapper( - entries.query # pyright: ignore[reportDeprecated], - ) - ) - self.unpublish_answer = async_to_streamed_response_wrapper( - entries.unpublish_answer, - ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index fc5a43a4..1314b7be 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -25,24 +25,8 @@ project_increment_queries_params, project_retrieve_analytics_params, ) -from .entries import ( - EntriesResource, - AsyncEntriesResource, - EntriesResourceWithRawResponse, - AsyncEntriesResourceWithRawResponse, - EntriesResourceWithStreamingResponse, - AsyncEntriesResourceWithStreamingResponse, -) from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven from ..._utils import maybe_transform, strip_not_given, async_maybe_transform -from .clusters import ( - ClustersResource, - AsyncClustersResource, - ClustersResourceWithRawResponse, - AsyncClustersResourceWithRawResponse, - ClustersResourceWithStreamingResponse, - AsyncClustersResourceWithStreamingResponse, -) from ..._compat import cached_property from .query_logs import ( QueryLogsResource, @@ -91,14 +75,6 @@ class ProjectsResource(SyncAPIResource): def access_keys(self) -> AccessKeysResource: return AccessKeysResource(self._client) - @cached_property - def entries(self) -> EntriesResource: - return EntriesResource(self._client) - - @cached_property - def clusters(self) -> ClustersResource: - return ClustersResource(self._client) - @cached_property def evals(self) -> EvalsResource: return EvalsResource(self._client) @@ -699,14 +675,6 @@ class AsyncProjectsResource(AsyncAPIResource): def access_keys(self) -> AsyncAccessKeysResource: return AsyncAccessKeysResource(self._client) - @cached_property - def entries(self) -> AsyncEntriesResource: - return AsyncEntriesResource(self._client) - - @cached_property - def clusters(self) -> AsyncClustersResource: - return AsyncClustersResource(self._client) - @cached_property def evals(self) -> AsyncEvalsResource: return AsyncEvalsResource(self._client) @@ -1345,14 +1313,6 @@ def __init__(self, projects: ProjectsResource) -> None: def access_keys(self) -> AccessKeysResourceWithRawResponse: return AccessKeysResourceWithRawResponse(self._projects.access_keys) - @cached_property - def entries(self) -> EntriesResourceWithRawResponse: - return EntriesResourceWithRawResponse(self._projects.entries) - - @cached_property - def clusters(self) -> ClustersResourceWithRawResponse: - return ClustersResourceWithRawResponse(self._projects.clusters) - @cached_property def evals(self) -> EvalsResourceWithRawResponse: return EvalsResourceWithRawResponse(self._projects.evals) @@ -1407,14 +1367,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None: def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: return AsyncAccessKeysResourceWithRawResponse(self._projects.access_keys) - @cached_property - def entries(self) -> AsyncEntriesResourceWithRawResponse: - return AsyncEntriesResourceWithRawResponse(self._projects.entries) - - @cached_property - def clusters(self) -> AsyncClustersResourceWithRawResponse: - return AsyncClustersResourceWithRawResponse(self._projects.clusters) - @cached_property def evals(self) -> AsyncEvalsResourceWithRawResponse: return AsyncEvalsResourceWithRawResponse(self._projects.evals) @@ -1469,14 +1421,6 @@ def __init__(self, projects: ProjectsResource) -> None: def access_keys(self) -> AccessKeysResourceWithStreamingResponse: return AccessKeysResourceWithStreamingResponse(self._projects.access_keys) - @cached_property - def entries(self) -> EntriesResourceWithStreamingResponse: - return EntriesResourceWithStreamingResponse(self._projects.entries) - - @cached_property - def clusters(self) -> ClustersResourceWithStreamingResponse: - return ClustersResourceWithStreamingResponse(self._projects.clusters) - @cached_property def evals(self) -> EvalsResourceWithStreamingResponse: return EvalsResourceWithStreamingResponse(self._projects.evals) @@ -1531,14 +1475,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None: def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: return AsyncAccessKeysResourceWithStreamingResponse(self._projects.access_keys) - @cached_property - def entries(self) -> AsyncEntriesResourceWithStreamingResponse: - return AsyncEntriesResourceWithStreamingResponse(self._projects.entries) - - @cached_property - def clusters(self) -> AsyncClustersResourceWithStreamingResponse: - return AsyncClustersResourceWithStreamingResponse(self._projects.clusters) - @cached_property def evals(self) -> AsyncEvalsResourceWithStreamingResponse: return AsyncEvalsResourceWithStreamingResponse(self._projects.evals) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 8a26aa00..4f754703 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -2,25 +2,16 @@ from __future__ import annotations -from .entry import Entry as Entry from .access_key_schema import AccessKeySchema as AccessKeySchema -from .entry_query_params import EntryQueryParams as EntryQueryParams from .eval_create_params import EvalCreateParams as EvalCreateParams from .eval_list_response import EvalListResponse as EvalListResponse from .eval_update_params import EvalUpdateParams as EvalUpdateParams -from .cluster_list_params import ClusterListParams as ClusterListParams -from .entry_create_params import EntryCreateParams as EntryCreateParams -from .entry_update_params import EntryUpdateParams as EntryUpdateParams -from .entry_query_response import EntryQueryResponse as EntryQueryResponse -from .cluster_list_response import ClusterListResponse as ClusterListResponse from .query_log_list_params import QueryLogListParams as QueryLogListParams -from .entry_notify_sme_params import EntryNotifySmeParams as EntryNotifySmeParams from .query_log_list_response import QueryLogListResponse as QueryLogListResponse from .remediation_list_params import RemediationListParams as RemediationListParams from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams -from .entry_notify_sme_response import EntryNotifySmeResponse as EntryNotifySmeResponse from .remediation_create_params import RemediationCreateParams as RemediationCreateParams from .remediation_list_response import RemediationListResponse as RemediationListResponse from .remediation_pause_response import RemediationPauseResponse as RemediationPauseResponse @@ -30,7 +21,6 @@ from .remediation_publish_response import RemediationPublishResponse as RemediationPublishResponse from .remediation_unpause_response import RemediationUnpauseResponse as RemediationUnpauseResponse from .remediation_retrieve_response import RemediationRetrieveResponse as RemediationRetrieveResponse -from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse from .query_log_list_by_group_params import QueryLogListByGroupParams as QueryLogListByGroupParams from .query_log_list_groups_response import QueryLogListGroupsResponse as QueryLogListGroupsResponse from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py deleted file mode 100644 index 20284d84..00000000 --- a/src/codex/types/projects/cluster_list_params.py +++ /dev/null @@ -1,34 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Optional -from typing_extensions import Literal, TypedDict - -__all__ = ["ClusterListParams"] - - -class ClusterListParams(TypedDict, total=False): - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] - - instruction_adherence_failure: Optional[Literal["html_format", "content_structure"]] - - limit: int - - offset: int - - order: Literal["asc", "desc"] - - sort: Optional[ - Literal[ - "created_at", - "answered_at", - "cluster_frequency_count", - "custom_rank", - "eval_score", - "html_format_score", - "content_structure_score", - ] - ] - - states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py deleted file mode 100644 index 1fc8bd5e..00000000 --- a/src/codex/types/projects/cluster_list_response.py +++ /dev/null @@ -1,214 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from datetime import datetime -from typing_extensions import Literal - -from ..._models import BaseModel - -__all__ = [ - "ClusterListResponse", - "ManagedMetadata", - "ManagedMetadataContentStructureScores", - "ManagedMetadataContextSufficiency", - "ManagedMetadataHTMLFormatScores", - "ManagedMetadataQueryEaseCustomized", - "ManagedMetadataResponseGroundedness", - "ManagedMetadataResponseHelpfulness", - "ManagedMetadataTrustworthiness", -] - - -class ManagedMetadataContentStructureScores(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataContextSufficiency(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataHTMLFormatScores(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataQueryEaseCustomized(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataResponseGroundedness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataResponseHelpfulness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataTrustworthiness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadata(BaseModel): - latest_context: Optional[str] = None - """The most recent context string.""" - - latest_entry_point: Optional[str] = None - """The most recent entry point string.""" - - latest_llm_response: Optional[str] = None - """The most recent LLM response string.""" - - latest_location: Optional[str] = None - """The most recent location string.""" - - content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None - """Holds a list of scores and computes aggregate statistics.""" - - context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None - """Holds a list of scores and computes aggregate statistics.""" - - contexts: Optional[List[str]] = None - - entry_points: Optional[List[str]] = None - - html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None - """Holds a list of scores and computes aggregate statistics.""" - - llm_responses: Optional[List[str]] = None - - locations: Optional[List[str]] = None - - query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None - """Holds a list of scores and computes aggregate statistics.""" - - response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None - """Holds a list of scores and computes aggregate statistics.""" - - response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None - """Holds a list of scores and computes aggregate statistics.""" - - trustworthiness: Optional[ManagedMetadataTrustworthiness] = None - """Holds a list of scores and computes aggregate statistics.""" - - -class ClusterListResponse(BaseModel): - id: str - - cluster_frequency_count: int - - created_at: datetime - - managed_metadata: ManagedMetadata - """Extract system-defined, managed metadata from client_query_metadata.""" - - project_id: str - - question: str - - state: Literal["unanswered", "draft", "published", "published_with_draft"] - - answer: Optional[str] = None - - answered_at: Optional[datetime] = None - - client_query_metadata: Optional[List[object]] = None - - content_structure_score: Optional[float] = None - - draft_answer: Optional[str] = None - - draft_answer_last_edited: Optional[datetime] = None - - eval_issue_type: Optional[str] = None - - eval_score: Optional[float] = None - - frequency_count: Optional[int] = None - """number of times the entry matched for a /query request""" - - html_format_score: Optional[float] = None - - representative_entry_id: Optional[str] = None diff --git a/src/codex/types/projects/cluster_list_variants_response.py b/src/codex/types/projects/cluster_list_variants_response.py deleted file mode 100644 index aa359058..00000000 --- a/src/codex/types/projects/cluster_list_variants_response.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from .entry import Entry -from ..._models import BaseModel - -__all__ = ["ClusterListVariantsResponse"] - - -class ClusterListVariantsResponse(BaseModel): - entries: List[Entry] - - total_count: int diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py deleted file mode 100644 index 3f7a86da..00000000 --- a/src/codex/types/projects/entry.py +++ /dev/null @@ -1,210 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from datetime import datetime -from typing_extensions import Literal - -from ..._models import BaseModel - -__all__ = [ - "Entry", - "ManagedMetadata", - "ManagedMetadataContentStructureScores", - "ManagedMetadataContextSufficiency", - "ManagedMetadataHTMLFormatScores", - "ManagedMetadataQueryEaseCustomized", - "ManagedMetadataResponseGroundedness", - "ManagedMetadataResponseHelpfulness", - "ManagedMetadataTrustworthiness", -] - - -class ManagedMetadataContentStructureScores(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataContextSufficiency(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataHTMLFormatScores(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataQueryEaseCustomized(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataResponseGroundedness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataResponseHelpfulness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadataTrustworthiness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class ManagedMetadata(BaseModel): - latest_context: Optional[str] = None - """The most recent context string.""" - - latest_entry_point: Optional[str] = None - """The most recent entry point string.""" - - latest_llm_response: Optional[str] = None - """The most recent LLM response string.""" - - latest_location: Optional[str] = None - """The most recent location string.""" - - content_structure_scores: Optional[ManagedMetadataContentStructureScores] = None - """Holds a list of scores and computes aggregate statistics.""" - - context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None - """Holds a list of scores and computes aggregate statistics.""" - - contexts: Optional[List[str]] = None - - entry_points: Optional[List[str]] = None - - html_format_scores: Optional[ManagedMetadataHTMLFormatScores] = None - """Holds a list of scores and computes aggregate statistics.""" - - llm_responses: Optional[List[str]] = None - - locations: Optional[List[str]] = None - - query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None - """Holds a list of scores and computes aggregate statistics.""" - - response_groundedness: Optional[ManagedMetadataResponseGroundedness] = None - """Holds a list of scores and computes aggregate statistics.""" - - response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None - """Holds a list of scores and computes aggregate statistics.""" - - trustworthiness: Optional[ManagedMetadataTrustworthiness] = None - """Holds a list of scores and computes aggregate statistics.""" - - -class Entry(BaseModel): - id: str - - created_at: datetime - - managed_metadata: ManagedMetadata - """Extract system-defined, managed metadata from client_query_metadata.""" - - project_id: str - - question: str - - state: Literal["unanswered", "draft", "published", "published_with_draft"] - - answer: Optional[str] = None - - answered_at: Optional[datetime] = None - - client_query_metadata: Optional[List[object]] = None - - content_structure_score: Optional[float] = None - - draft_answer: Optional[str] = None - - draft_answer_last_edited: Optional[datetime] = None - - eval_issue_type: Optional[str] = None - - eval_score: Optional[float] = None - - frequency_count: Optional[int] = None - """number of times the entry matched for a /query request""" - - html_format_score: Optional[float] = None diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py deleted file mode 100644 index f06846bb..00000000 --- a/src/codex/types/projects/entry_create_params.py +++ /dev/null @@ -1,28 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Iterable, Optional -from typing_extensions import Required, Annotated, TypedDict - -from ..._utils import PropertyInfo - -__all__ = ["EntryCreateParams"] - - -class EntryCreateParams(TypedDict, total=False): - question: Required[str] - - answer: Optional[str] - - client_query_metadata: Iterable[object] - - draft_answer: Optional[str] - - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] - - x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] - - x_source: Annotated[str, PropertyInfo(alias="x-source")] - - x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] diff --git a/src/codex/types/projects/entry_notify_sme_params.py b/src/codex/types/projects/entry_notify_sme_params.py deleted file mode 100644 index 3d06d1a6..00000000 --- a/src/codex/types/projects/entry_notify_sme_params.py +++ /dev/null @@ -1,30 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, Required, TypedDict - -__all__ = ["EntryNotifySmeParams", "ViewContext"] - - -class EntryNotifySmeParams(TypedDict, total=False): - project_id: Required[str] - - email: Required[str] - - view_context: Required[ViewContext] - - -class ViewContext(TypedDict, total=False): - page: Required[int] - - filter: Literal[ - "unanswered", - "answered", - "all", - "hallucination", - "search_failure", - "unhelpful", - "difficult_query", - "unsupported", - ] diff --git a/src/codex/types/projects/entry_notify_sme_response.py b/src/codex/types/projects/entry_notify_sme_response.py deleted file mode 100644 index b3c5b373..00000000 --- a/src/codex/types/projects/entry_notify_sme_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from ..._models import BaseModel - -__all__ = ["EntryNotifySmeResponse"] - - -class EntryNotifySmeResponse(BaseModel): - entry_id: str - - recipient_email: str - - status: str diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py deleted file mode 100644 index 2ba33b82..00000000 --- a/src/codex/types/projects/entry_query_params.py +++ /dev/null @@ -1,300 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, List, Union, Iterable, Optional -from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict - -from ..._utils import PropertyInfo - -__all__ = [ - "EntryQueryParams", - "QueryMetadata", - "QueryMetadataContextUnionMember3", - "QueryMetadataMessage", - "QueryMetadataMessageChatCompletionDeveloperMessageParam", - "QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1", - "QueryMetadataMessageChatCompletionSystemMessageParam", - "QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1", - "QueryMetadataMessageChatCompletionUserMessageParam", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File", - "QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile", - "QueryMetadataMessageChatCompletionAssistantMessageParam", - "QueryMetadataMessageChatCompletionAssistantMessageParamAudio", - "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1", - "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam", - "QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam", - "QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall", - "QueryMetadataMessageChatCompletionAssistantMessageParamToolCall", - "QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction", - "QueryMetadataMessageChatCompletionToolMessageParam", - "QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1", - "QueryMetadataMessageChatCompletionFunctionMessageParam", -] - - -class EntryQueryParams(TypedDict, total=False): - question: Required[str] - - use_llm_matching: bool - - client_metadata: Optional[object] - """Deprecated: Use query_metadata instead""" - - query_metadata: Optional[QueryMetadata] - """Optional logging data that can be provided by the client.""" - - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] - - x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] - - x_source: Annotated[str, PropertyInfo(alias="x-source")] - - x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] - - -class QueryMetadataContextUnionMember3(TypedDict, total=False): - content: Required[str] - """The actual content/text of the document.""" - - id: Optional[str] - """Unique identifier for the document. Useful for tracking documents""" - - source: Optional[str] - """Source or origin of the document. Useful for citations.""" - - tags: Optional[List[str]] - """Tags or categories for the document. Useful for filtering""" - - title: Optional[str] - """Title or heading of the document. Useful for display and context.""" - - -class QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): - text: Required[str] - - type: Required[Literal["text"]] - - -class QueryMetadataMessageChatCompletionDeveloperMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionDeveloperMessageParamContentUnionMember1]]] - - role: Required[Literal["developer"]] - - name: str - - -class QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False): - text: Required[str] - - type: Required[Literal["text"]] - - -class QueryMetadataMessageChatCompletionSystemMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionSystemMessageParamContentUnionMember1]]] - - role: Required[Literal["system"]] - - name: str - - -class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam( - TypedDict, total=False -): - text: Required[str] - - type: Required[Literal["text"]] - - -class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL( - TypedDict, total=False -): - url: Required[str] - - detail: Literal["auto", "low", "high"] - - -class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam( - TypedDict, total=False -): - image_url: Required[ - QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL - ] - - type: Required[Literal["image_url"]] - - -class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( - TypedDict, total=False -): - data: Required[str] - - format: Required[Literal["wav", "mp3"]] - - -class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam( - TypedDict, total=False -): - input_audio: Required[ - QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio - ] - - type: Required[Literal["input_audio"]] - - -class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False): - file_data: str - - file_id: str - - filename: str - - -class QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False): - file: Required[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1FileFile] - - type: Required[Literal["file"]] - - -QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[ - QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam, - QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam, - QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam, - QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1File, -] - - -class QueryMetadataMessageChatCompletionUserMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionUserMessageParamContentUnionMember1]]] - - role: Required[Literal["user"]] - - name: str - - -class QueryMetadataMessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False): - id: Required[str] - - -class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam( - TypedDict, total=False -): - text: Required[str] - - type: Required[Literal["text"]] - - -class QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam( - TypedDict, total=False -): - refusal: Required[str] - - type: Required[Literal["refusal"]] - - -QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[ - QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam, - QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam, -] - - -class QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False): - arguments: Required[str] - - name: Required[str] - - -class QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False): - arguments: Required[str] - - name: Required[str] - - -class QueryMetadataMessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False): - id: Required[str] - - function: Required[QueryMetadataMessageChatCompletionAssistantMessageParamToolCallFunction] - - type: Required[Literal["function"]] - - -class QueryMetadataMessageChatCompletionAssistantMessageParam(TypedDict, total=False): - role: Required[Literal["assistant"]] - - audio: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamAudio] - - content: Union[str, Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamContentUnionMember1], None] - - function_call: Optional[QueryMetadataMessageChatCompletionAssistantMessageParamFunctionCall] - - name: str - - refusal: Optional[str] - - tool_calls: Iterable[QueryMetadataMessageChatCompletionAssistantMessageParamToolCall] - - -class QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False): - text: Required[str] - - type: Required[Literal["text"]] - - -class QueryMetadataMessageChatCompletionToolMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[QueryMetadataMessageChatCompletionToolMessageParamContentUnionMember1]]] - - role: Required[Literal["tool"]] - - tool_call_id: Required[str] - - -class QueryMetadataMessageChatCompletionFunctionMessageParam(TypedDict, total=False): - content: Required[Optional[str]] - - name: Required[str] - - role: Required[Literal["function"]] - - -QueryMetadataMessage: TypeAlias = Union[ - QueryMetadataMessageChatCompletionDeveloperMessageParam, - QueryMetadataMessageChatCompletionSystemMessageParam, - QueryMetadataMessageChatCompletionUserMessageParam, - QueryMetadataMessageChatCompletionAssistantMessageParam, - QueryMetadataMessageChatCompletionToolMessageParam, - QueryMetadataMessageChatCompletionFunctionMessageParam, -] - - -class QueryMetadata(TypedDict, total=False): - context: Union[str, List[str], Iterable[object], Iterable[QueryMetadataContextUnionMember3], None] - """RAG context used for the query""" - - custom_metadata: Optional[object] - """Arbitrary metadata supplied by the user/system""" - - eval_scores: Optional[Dict[str, float]] - """Evaluation scores for the original response""" - - evaluated_response: Optional[str] - """The response being evaluated from the RAG system(before any remediation)""" - - messages: Optional[Iterable[QueryMetadataMessage]] - """Optional message history to provide conversation context for the query. - - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. - """ - - original_question: Optional[str] - """The original question that was asked before any rewriting or processing. - - For all non-conversational RAG, original_question should be the same as the - final question seen in Codex. - """ diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py deleted file mode 100644 index cd5a4c97..00000000 --- a/src/codex/types/projects/entry_query_response.py +++ /dev/null @@ -1,194 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from ..._models import BaseModel - -__all__ = [ - "EntryQueryResponse", - "Entry", - "EntryManagedMetadata", - "EntryManagedMetadataContentStructureScores", - "EntryManagedMetadataContextSufficiency", - "EntryManagedMetadataHTMLFormatScores", - "EntryManagedMetadataQueryEaseCustomized", - "EntryManagedMetadataResponseGroundedness", - "EntryManagedMetadataResponseHelpfulness", - "EntryManagedMetadataTrustworthiness", -] - - -class EntryManagedMetadataContentStructureScores(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class EntryManagedMetadataContextSufficiency(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class EntryManagedMetadataHTMLFormatScores(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class EntryManagedMetadataQueryEaseCustomized(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class EntryManagedMetadataResponseGroundedness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class EntryManagedMetadataResponseHelpfulness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class EntryManagedMetadataTrustworthiness(BaseModel): - average: Optional[float] = None - """The average of all scores.""" - - latest: Optional[float] = None - """The most recent score.""" - - max: Optional[float] = None - """The maximum score.""" - - min: Optional[float] = None - """The minimum score.""" - - scores: Optional[List[float]] = None - - -class EntryManagedMetadata(BaseModel): - latest_context: Optional[str] = None - """The most recent context string.""" - - latest_entry_point: Optional[str] = None - """The most recent entry point string.""" - - latest_llm_response: Optional[str] = None - """The most recent LLM response string.""" - - latest_location: Optional[str] = None - """The most recent location string.""" - - content_structure_scores: Optional[EntryManagedMetadataContentStructureScores] = None - """Holds a list of scores and computes aggregate statistics.""" - - context_sufficiency: Optional[EntryManagedMetadataContextSufficiency] = None - """Holds a list of scores and computes aggregate statistics.""" - - contexts: Optional[List[str]] = None - - entry_points: Optional[List[str]] = None - - html_format_scores: Optional[EntryManagedMetadataHTMLFormatScores] = None - """Holds a list of scores and computes aggregate statistics.""" - - llm_responses: Optional[List[str]] = None - - locations: Optional[List[str]] = None - - query_ease_customized: Optional[EntryManagedMetadataQueryEaseCustomized] = None - """Holds a list of scores and computes aggregate statistics.""" - - response_groundedness: Optional[EntryManagedMetadataResponseGroundedness] = None - """Holds a list of scores and computes aggregate statistics.""" - - response_helpfulness: Optional[EntryManagedMetadataResponseHelpfulness] = None - """Holds a list of scores and computes aggregate statistics.""" - - trustworthiness: Optional[EntryManagedMetadataTrustworthiness] = None - """Holds a list of scores and computes aggregate statistics.""" - - -class Entry(BaseModel): - id: str - - managed_metadata: EntryManagedMetadata - """Extract system-defined, managed metadata from client_query_metadata.""" - - question: str - - answer: Optional[str] = None - - client_query_metadata: Optional[List[object]] = None - - draft_answer: Optional[str] = None - - -class EntryQueryResponse(BaseModel): - entry: Entry - - answer: Optional[str] = None diff --git a/src/codex/types/projects/entry_update_params.py b/src/codex/types/projects/entry_update_params.py deleted file mode 100644 index aac256f9..00000000 --- a/src/codex/types/projects/entry_update_params.py +++ /dev/null @@ -1,18 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Required, TypedDict - -__all__ = ["EntryUpdateParams"] - - -class EntryUpdateParams(TypedDict, total=False): - project_id: Required[str] - - answer: Optional[str] - - draft_answer: Optional[str] - - question: Optional[str] diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 9d1e0e69..c294d93d 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -71,12 +71,16 @@ class QueryLogsByGroupQueryLog(BaseModel): is_bad_response: bool + needs_review: bool + project_id: str question: str remediation_id: str + remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] + was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 7b77cc05..ce5806b6 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -78,7 +78,7 @@ class QueryLogGroup(BaseModel): remediation_id: str - status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] + remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] total_count: int diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 8bb61283..5214cff5 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -69,12 +69,16 @@ class QueryLogRetrieveResponse(BaseModel): is_bad_response: bool + needs_review: bool + project_id: str question: str remediation_id: str + remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] + was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" diff --git a/tests/api_resources/projects/test_clusters.py b/tests/api_resources/projects/test_clusters.py deleted file mode 100644 index 87734277..00000000 --- a/tests/api_resources/projects/test_clusters.py +++ /dev/null @@ -1,247 +0,0 @@ -# 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 codex import Codex, AsyncCodex -from tests.utils import assert_matches_type -from codex.pagination import SyncOffsetPageClusters, AsyncOffsetPageClusters -from codex.types.projects import ClusterListResponse, ClusterListVariantsResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestClusters: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - def test_method_list(self, client: Codex) -> None: - cluster = client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_list_with_all_params(self, client: Codex) -> None: - cluster = client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - eval_issue_types=["hallucination"], - instruction_adherence_failure="html_format", - limit=1, - offset=0, - order="asc", - sort="created_at", - states=["unanswered"], - ) - assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.clusters.with_raw_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - cluster = response.parse() - assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.clusters.with_streaming_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - cluster = response.parse() - assert_matches_type(SyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_list(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.clusters.with_raw_response.list( - project_id="", - ) - - @pytest.mark.skip() - @parametrize - def test_method_list_variants(self, client: Codex) -> None: - cluster = client.projects.clusters.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_list_variants(self, client: Codex) -> None: - response = client.projects.clusters.with_raw_response.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - cluster = response.parse() - assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_list_variants(self, client: Codex) -> None: - with client.projects.clusters.with_streaming_response.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - cluster = response.parse() - assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_list_variants(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.clusters.with_raw_response.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises( - ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''" - ): - client.projects.clusters.with_raw_response.list_variants( - representative_entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - -class TestAsyncClusters: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip() - @parametrize - async def test_method_list(self, async_client: AsyncCodex) -> None: - cluster = await async_client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: - cluster = await async_client.projects.clusters.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - eval_issue_types=["hallucination"], - instruction_adherence_failure="html_format", - limit=1, - offset=0, - order="asc", - sort="created_at", - states=["unanswered"], - ) - assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.clusters.with_raw_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - cluster = await response.parse() - assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.clusters.with_streaming_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - cluster = await response.parse() - assert_matches_type(AsyncOffsetPageClusters[ClusterListResponse], cluster, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_list(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.clusters.with_raw_response.list( - project_id="", - ) - - @pytest.mark.skip() - @parametrize - async def test_method_list_variants(self, async_client: AsyncCodex) -> None: - cluster = await async_client.projects.clusters.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_list_variants(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.clusters.with_raw_response.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - cluster = await response.parse() - assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_list_variants(self, async_client: AsyncCodex) -> None: - async with async_client.projects.clusters.with_streaming_response.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - cluster = await response.parse() - assert_matches_type(ClusterListVariantsResponse, cluster, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_list_variants(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.clusters.with_raw_response.list_variants( - representative_entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises( - ValueError, match=r"Expected a non-empty value for `representative_entry_id` but received ''" - ): - await async_client.projects.clusters.with_raw_response.list_variants( - representative_entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py deleted file mode 100644 index 1b077d10..00000000 --- a/tests/api_resources/projects/test_entries.py +++ /dev/null @@ -1,1014 +0,0 @@ -# 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 codex import Codex, AsyncCodex -from tests.utils import assert_matches_type -from codex.types.projects import ( - Entry, - EntryQueryResponse, - EntryNotifySmeResponse, -) - -# pyright: reportDeprecated=false - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestEntries: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - def test_method_create(self, client: Codex) -> None: - entry = client.projects.entries.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_create_with_all_params(self, client: Codex) -> None: - entry = client.projects.entries.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - answer="answer", - client_query_metadata=[{}], - draft_answer="draft_answer", - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_create(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_create(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_create(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.create( - project_id="", - question="question", - ) - - @pytest.mark.skip() - @parametrize - def test_method_retrieve(self, client: Codex) -> None: - entry = client.projects.entries.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_retrieve(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_retrieve(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_retrieve(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.entries.with_raw_response.retrieve( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - def test_method_update(self, client: Codex) -> None: - entry = client.projects.entries.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_update_with_all_params(self, client: Codex) -> None: - entry = client.projects.entries.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - draft_answer="draft_answer", - question="question", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_update(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_update(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_update(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.entries.with_raw_response.update( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - def test_method_delete(self, client: Codex) -> None: - entry = client.projects.entries.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert entry is None - - @pytest.mark.skip() - @parametrize - def test_raw_response_delete(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert entry is None - - @pytest.mark.skip() - @parametrize - def test_streaming_response_delete(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert entry is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_delete(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.entries.with_raw_response.delete( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - def test_method_notify_sme(self, client: Codex) -> None: - entry = client.projects.entries.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_notify_sme_with_all_params(self, client: Codex) -> None: - entry = client.projects.entries.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={ - "page": 0, - "filter": "unanswered", - }, - ) - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_notify_sme(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_notify_sme(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_notify_sme(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - email="email", - view_context={"page": 0}, - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.entries.with_raw_response.notify_sme( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) - - @pytest.mark.skip() - @parametrize - def test_method_publish_draft_answer(self, client: Codex) -> None: - entry = client.projects.entries.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_publish_draft_answer(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_publish_draft_answer(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_publish_draft_answer(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.entries.with_raw_response.publish_draft_answer( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - def test_method_query(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - entry = client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_query_with_all_params(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - entry = client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - use_llm_matching=True, - client_metadata={}, - query_metadata={ - "context": "string", - "custom_metadata": {}, - "eval_scores": {"foo": 0}, - "evaluated_response": "evaluated_response", - "messages": [ - { - "content": "string", - "role": "developer", - "name": "name", - } - ], - "original_question": "original_question", - }, - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) - - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_query(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - response = client.projects.entries.with_raw_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_query(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - with client.projects.entries.with_streaming_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_query(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.query( - project_id="", - question="question", - ) - - @pytest.mark.skip() - @parametrize - def test_method_unpublish_answer(self, client: Codex) -> None: - entry = client.projects.entries.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_unpublish_answer(self, client: Codex) -> None: - response = client.projects.entries.with_raw_response.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_unpublish_answer(self, client: Codex) -> None: - with client.projects.entries.with_streaming_response.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_unpublish_answer(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.entries.with_raw_response.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - client.projects.entries.with_raw_response.unpublish_answer( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - -class TestAsyncEntries: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip() - @parametrize - async def test_method_create(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - answer="answer", - client_query_metadata=[{}], - draft_answer="draft_answer", - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_create(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_create(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.create( - project_id="", - question="question", - ) - - @pytest.mark.skip() - @parametrize - async def test_method_retrieve(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.retrieve( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.entries.with_raw_response.retrieve( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - async def test_method_update(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - draft_answer="draft_answer", - question="question", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_update(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_update(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.update( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.entries.with_raw_response.update( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - async def test_method_delete(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert entry is None - - @pytest.mark.skip() - @parametrize - async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert entry is None - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert entry is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_delete(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.delete( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.entries.with_raw_response.delete( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - async def test_method_notify_sme(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_notify_sme_with_all_params(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={ - "page": 0, - "filter": "unanswered", - }, - ) - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_notify_sme(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_notify_sme(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_notify_sme(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.notify_sme( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - email="email", - view_context={"page": 0}, - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.entries.with_raw_response.notify_sme( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - email="email", - view_context={"page": 0}, - ) - - @pytest.mark.skip() - @parametrize - async def test_method_publish_draft_answer(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_publish_draft_answer(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_publish_draft_answer(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_publish_draft_answer(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.publish_draft_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.entries.with_raw_response.publish_draft_answer( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - @pytest.mark.skip() - @parametrize - async def test_method_query(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - entry = await async_client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - entry = await async_client.projects.entries.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - use_llm_matching=True, - client_metadata={}, - query_metadata={ - "context": "string", - "custom_metadata": {}, - "eval_scores": {"foo": 0}, - "evaluated_response": "evaluated_response", - "messages": [ - { - "content": "string", - "role": "developer", - "name": "name", - } - ], - "original_question": "original_question", - }, - x_client_library_version="x-client-library-version", - x_integration_type="x-integration-type", - x_source="x-source", - x_stainless_package_version="x-stainless-package-version", - ) - - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_query(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - response = await async_client.projects.entries.with_raw_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_query(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - async with async_client.projects.entries.with_streaming_response.query( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(EntryQueryResponse, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_query(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.query( - project_id="", - question="question", - ) - - @pytest.mark.skip() - @parametrize - async def test_method_unpublish_answer(self, async_client: AsyncCodex) -> None: - entry = await async_client.projects.entries.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_unpublish_answer(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.entries.with_raw_response.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_unpublish_answer(self, async_client: AsyncCodex) -> None: - async with async_client.projects.entries.with_streaming_response.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - entry = await response.parse() - assert_matches_type(Entry, entry, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_unpublish_answer(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.entries.with_raw_response.unpublish_answer( - entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): - await async_client.projects.entries.with_raw_response.unpublish_answer( - entry_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) From 84d6674954b7bcf161e0c582a1a28c8adb87e4c3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 05:00:55 +0000 Subject: [PATCH 186/320] chore(ci): change upload type --- .github/workflows/ci.yml | 18 ++++++++++++++++-- scripts/utils/upload-artifact.sh | 12 +++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbb722b8..a6979ebb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,10 +35,10 @@ jobs: - name: Run lints run: ./scripts/lint - upload: + build: if: github.repository == 'stainless-sdks/codex-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 - name: upload + name: build permissions: contents: read id-token: write @@ -46,6 +46,20 @@ jobs: steps: - uses: actions/checkout@v4 + - 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 id: github-oidc uses: actions/github-script@v6 diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 62d150a4..aac03848 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash set -exuo pipefail -RESPONSE=$(curl -X POST "$URL" \ +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ -H "Authorization: Bearer $AUTH" \ -H "Content-Type: application/json") @@ -12,13 +14,13 @@ if [[ "$SIGNED_URL" == "null" ]]; then exit 1 fi -UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ - -H "Content-Type: application/gzip" \ - --data-binary @- "$SIGNED_URL" 2>&1) +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 --pre 'https://pkg.stainless.com/s/codex-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/codex-python/$SHA/$FILENAME'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From 632d1ee76670fa77564ced8443b22336913ebcc8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 19:17:27 +0000 Subject: [PATCH 187/320] feat(api): api update --- .stats.yml | 2 +- .../projects/query_log_list_by_group_response.py | 11 +++++++++++ .../types/projects/query_log_list_groups_response.py | 9 +++++++++ src/codex/types/projects/query_log_list_response.py | 9 +++++++++ .../types/projects/query_log_retrieve_response.py | 9 +++++++++ .../remediation_list_resolved_logs_response.py | 9 +++++++++ 6 files changed, 48 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index cc72a8a8..08bc5f74 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: b2b026661b19d060e5eac490807fe445 +openapi_spec_hash: 06bae836118b9c7ac2cf1b5e1a576296 config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index c294d93d..eeb4b638 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -13,6 +13,7 @@ "QueryLogsByGroupQueryLogFormattedEscalationEvalScores", "QueryLogsByGroupQueryLogFormattedEvalScores", "QueryLogsByGroupQueryLogFormattedGuardrailEvalScores", + "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", ] @@ -35,6 +36,12 @@ class QueryLogsByGroupQueryLogFormattedGuardrailEvalScores(BaseModel): status: Literal["pass", "fail"] +class QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + class QueryLogsByGroupQueryLogContext(BaseModel): content: str """The actual content/text of the document.""" @@ -69,6 +76,10 @@ class QueryLogsByGroupQueryLog(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedGuardrailEvalScores]] = None + formatted_non_guardrail_eval_scores: Optional[ + Dict[str, QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores] + ] = None + is_bad_response: bool needs_review: bool diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index ce5806b6..074dfaca 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -12,6 +12,7 @@ "QueryLogGroupFormattedEscalationEvalScores", "QueryLogGroupFormattedEvalScores", "QueryLogGroupFormattedGuardrailEvalScores", + "QueryLogGroupFormattedNonGuardrailEvalScores", "QueryLogGroupContext", ] @@ -34,6 +35,12 @@ class QueryLogGroupFormattedGuardrailEvalScores(BaseModel): status: Literal["pass", "fail"] +class QueryLogGroupFormattedNonGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + class QueryLogGroupContext(BaseModel): content: str """The actual content/text of the document.""" @@ -68,6 +75,8 @@ class QueryLogGroup(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedGuardrailEvalScores]] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedNonGuardrailEvalScores]] = None + is_bad_response: bool needs_review: bool diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index fa04904d..09a9067a 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -12,6 +12,7 @@ "QueryLogFormattedEscalationEvalScores", "QueryLogFormattedEvalScores", "QueryLogFormattedGuardrailEvalScores", + "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", ] @@ -34,6 +35,12 @@ class QueryLogFormattedGuardrailEvalScores(BaseModel): status: Literal["pass", "fail"] +class QueryLogFormattedNonGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + class QueryLogContext(BaseModel): content: str """The actual content/text of the document.""" @@ -68,6 +75,8 @@ class QueryLog(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None + is_bad_response: bool project_id: str diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 5214cff5..3b2277d5 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -11,6 +11,7 @@ "FormattedEscalationEvalScores", "FormattedEvalScores", "FormattedGuardrailEvalScores", + "FormattedNonGuardrailEvalScores", "Context", ] @@ -33,6 +34,12 @@ class FormattedGuardrailEvalScores(BaseModel): status: Literal["pass", "fail"] +class FormattedNonGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + class Context(BaseModel): content: str """The actual content/text of the document.""" @@ -67,6 +74,8 @@ class QueryLogRetrieveResponse(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None + is_bad_response: bool needs_review: bool diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index b2315aa4..7e40fb45 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -12,6 +12,7 @@ "QueryLogFormattedEscalationEvalScores", "QueryLogFormattedEvalScores", "QueryLogFormattedGuardrailEvalScores", + "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", ] @@ -34,6 +35,12 @@ class QueryLogFormattedGuardrailEvalScores(BaseModel): status: Literal["pass", "fail"] +class QueryLogFormattedNonGuardrailEvalScores(BaseModel): + score: float + + status: Literal["pass", "fail"] + + class QueryLogContext(BaseModel): content: str """The actual content/text of the document.""" @@ -68,6 +75,8 @@ class QueryLog(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None + is_bad_response: bool project_id: str From bb81d205574a987b632b1cead05c716313aad25a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:17:40 +0000 Subject: [PATCH 188/320] feat(api): api update --- .stats.yml | 2 +- requirements-dev.lock | 2 +- requirements.lock | 2 +- src/codex/resources/projects/query_logs.py | 12 ++++++------ src/codex/types/project_validate_response.py | 15 +++++++++++++-- .../projects/query_log_list_by_group_params.py | 2 +- .../projects/query_log_list_by_group_response.py | 12 ++++++++++++ .../projects/query_log_list_groups_params.py | 2 +- .../projects/query_log_list_groups_response.py | 12 ++++++++++++ src/codex/types/projects/query_log_list_params.py | 2 +- .../types/projects/query_log_list_response.py | 12 ++++++++++++ .../types/projects/query_log_retrieve_response.py | 12 ++++++++++++ .../remediation_list_resolved_logs_response.py | 12 ++++++++++++ 13 files changed, 85 insertions(+), 14 deletions(-) diff --git a/.stats.yml b/.stats.yml index 08bc5f74..371ed3d3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 06bae836118b9c7ac2cf1b5e1a576296 +openapi_spec_hash: f17890d85522687a4c68702da9ad2efb config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/requirements-dev.lock b/requirements-dev.lock index 11a27038..a84b5f44 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -56,7 +56,7 @@ httpx==0.28.1 # via codex-sdk # via httpx-aiohttp # via respx -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via codex-sdk idna==3.4 # via anyio diff --git a/requirements.lock b/requirements.lock index 5eda9d9c..a0807d8f 100644 --- a/requirements.lock +++ b/requirements.lock @@ -43,7 +43,7 @@ httpcore==1.0.2 httpx==0.28.1 # via codex-sdk # via httpx-aiohttp -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via codex-sdk idna==3.4 # via anyio diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index e97243ec..148df9f6 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -99,7 +99,7 @@ def list( order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, @@ -184,7 +184,7 @@ def list_by_group( order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, remediation_ids: List[str] | NotGiven = NOT_GIVEN, @@ -276,7 +276,7 @@ def list_groups( order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] @@ -457,7 +457,7 @@ async def list( order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, @@ -542,7 +542,7 @@ async def list_by_group( order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, remediation_ids: List[str] | NotGiven = NOT_GIVEN, @@ -634,7 +634,7 @@ async def list_groups( order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index db65f676..3b06db2d 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -1,10 +1,18 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Dict, List, Optional from .._models import BaseModel -__all__ = ["ProjectValidateResponse", "EvalScores"] +__all__ = ["ProjectValidateResponse", "DeterministicGuardrailsResults", "EvalScores"] + + +class DeterministicGuardrailsResults(BaseModel): + guardrail_name: str + + should_guardrail: bool + + matches: Optional[List[str]] = None class EvalScores(BaseModel): @@ -22,6 +30,9 @@ class EvalScores(BaseModel): class ProjectValidateResponse(BaseModel): + deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None + """Results from deterministic guardrails applied to the response.""" + escalated_to_sme: bool """ True if the question should be escalated to Codex for an SME to review, False diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index b44970a1..90bd3867 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -40,7 +40,7 @@ class QueryLogListByGroupParams(TypedDict, total=False): """Filter by evals that passed""" primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index eeb4b638..16850735 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -15,6 +15,7 @@ "QueryLogsByGroupQueryLogFormattedGuardrailEvalScores", "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", + "QueryLogsByGroupQueryLogDeterministicGuardrailsResults", ] @@ -59,6 +60,14 @@ class QueryLogsByGroupQueryLogContext(BaseModel): """Title or heading of the document. Useful for display and context.""" +class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): + guardrail_name: str + + should_guardrail: bool + + matches: Optional[List[str]] = None + + class QueryLogsByGroupQueryLog(BaseModel): id: str @@ -104,6 +113,9 @@ class QueryLogsByGroupQueryLog(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + deterministic_guardrails_results: Optional[Dict[str, QueryLogsByGroupQueryLogDeterministicGuardrailsResults]] = None + """Results of deterministic guardrails applied to the query""" + escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index 94d549f5..cd82d9a7 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -40,7 +40,7 @@ class QueryLogListGroupsParams(TypedDict, total=False): """Filter by evals that passed""" primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 074dfaca..e77dd87e 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -14,6 +14,7 @@ "QueryLogGroupFormattedGuardrailEvalScores", "QueryLogGroupFormattedNonGuardrailEvalScores", "QueryLogGroupContext", + "QueryLogGroupDeterministicGuardrailsResults", ] @@ -58,6 +59,14 @@ class QueryLogGroupContext(BaseModel): """Title or heading of the document. Useful for display and context.""" +class QueryLogGroupDeterministicGuardrailsResults(BaseModel): + guardrail_name: str + + should_guardrail: bool + + matches: Optional[List[str]] = None + + class QueryLogGroup(BaseModel): id: str @@ -103,6 +112,9 @@ class QueryLogGroup(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + deterministic_guardrails_results: Optional[Dict[str, QueryLogGroupDeterministicGuardrailsResults]] = None + """Results of deterministic guardrails applied to the query""" + escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 0f72b241..5892d3c9 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -37,7 +37,7 @@ class QueryLogListParams(TypedDict, total=False): """Filter by evals that passed""" primary_eval_issue: Optional[ - List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "unsupported"]] + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 09a9067a..147478cf 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -14,6 +14,7 @@ "QueryLogFormattedGuardrailEvalScores", "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", + "QueryLogDeterministicGuardrailsResults", ] @@ -58,6 +59,14 @@ class QueryLogContext(BaseModel): """Title or heading of the document. Useful for display and context.""" +class QueryLogDeterministicGuardrailsResults(BaseModel): + guardrail_name: str + + should_guardrail: bool + + matches: Optional[List[str]] = None + + class QueryLog(BaseModel): id: str @@ -97,6 +106,9 @@ class QueryLog(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + deterministic_guardrails_results: Optional[Dict[str, QueryLogDeterministicGuardrailsResults]] = None + """Results of deterministic guardrails applied to the query""" + escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 3b2277d5..380bacba 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -13,6 +13,7 @@ "FormattedGuardrailEvalScores", "FormattedNonGuardrailEvalScores", "Context", + "DeterministicGuardrailsResults", ] @@ -57,6 +58,14 @@ class Context(BaseModel): """Title or heading of the document. Useful for display and context.""" +class DeterministicGuardrailsResults(BaseModel): + guardrail_name: str + + should_guardrail: bool + + matches: Optional[List[str]] = None + + class QueryLogRetrieveResponse(BaseModel): id: str @@ -100,6 +109,9 @@ class QueryLogRetrieveResponse(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None + """Results of deterministic guardrails applied to the query""" + escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 7e40fb45..876e7cec 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -14,6 +14,7 @@ "QueryLogFormattedGuardrailEvalScores", "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", + "QueryLogDeterministicGuardrailsResults", ] @@ -58,6 +59,14 @@ class QueryLogContext(BaseModel): """Title or heading of the document. Useful for display and context.""" +class QueryLogDeterministicGuardrailsResults(BaseModel): + guardrail_name: str + + should_guardrail: bool + + matches: Optional[List[str]] = None + + class QueryLog(BaseModel): id: str @@ -97,6 +106,9 @@ class QueryLog(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" + deterministic_guardrails_results: Optional[Dict[str, QueryLogDeterministicGuardrailsResults]] = None + """Results of deterministic guardrails applied to the query""" + escalated: Optional[bool] = None """If true, the question was escalated to Codex for an SME to review""" From 29c6653719046b08ba309d881447aa5727a8bcc8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:36:29 +0000 Subject: [PATCH 189/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 42 ++-- src/codex/types/project_validate_params.py | 279 ++++++++++++++++++++- tests/api_resources/test_projects.py | 34 ++- 4 files changed, 315 insertions(+), 42 deletions(-) diff --git a/.stats.yml b/.stats.yml index 371ed3d3..c8908e83 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: f17890d85522687a4c68702da9ad2efb +openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09 config_hash: 14b2643a0ec60cf326dfed00939644ff diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 1314b7be..dc01b112 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -488,17 +488,18 @@ def validate( project_id: str, *, context: str, - prompt: str, query: str, - response: str, + response: project_validate_params.Response, use_llm_matching: bool | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, + messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, + prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -526,9 +527,8 @@ def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. - messages: Optional message history to provide conversation context for the query. Used to - rewrite query into a self-contained version of itself. If not provided, the - query will be treated as self-contained. + messages: Message history to provide conversation context for the query. Messages contain + up to and including the latest user prompt to the LLM. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -615,8 +615,14 @@ def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be + generated from the messages. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + rewritten_question: The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -643,7 +649,6 @@ def validate( body=maybe_transform( { "context": context, - "prompt": prompt, "query": query, "response": response, "constrain_outputs": constrain_outputs, @@ -652,7 +657,9 @@ def validate( "eval_scores": eval_scores, "messages": messages, "options": options, + "prompt": prompt, "quality_preset": quality_preset, + "rewritten_question": rewritten_question, "task": task, }, project_validate_params.ProjectValidateParams, @@ -1090,17 +1097,18 @@ async def validate( project_id: str, *, context: str, - prompt: str, query: str, - response: str, + response: project_validate_params.Response, use_llm_matching: bool | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, + messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, + prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -1128,9 +1136,8 @@ async def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. - messages: Optional message history to provide conversation context for the query. Used to - rewrite query into a self-contained version of itself. If not provided, the - query will be treated as self-contained. + messages: Message history to provide conversation context for the query. Messages contain + up to and including the latest user prompt to the LLM. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -1217,8 +1224,14 @@ async def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be + generated from the messages. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + rewritten_question: The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1245,7 +1258,6 @@ async def validate( body=await async_maybe_transform( { "context": context, - "prompt": prompt, "query": query, "response": response, "constrain_outputs": constrain_outputs, @@ -1254,7 +1266,9 @@ async def validate( "eval_scores": eval_scores, "messages": messages, "options": options, + "prompt": prompt, "quality_preset": quality_preset, + "rewritten_question": rewritten_question, "task": task, }, project_validate_params.ProjectValidateParams, diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 8b38ebfa..0862cbc1 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +import builtins from typing import Dict, List, Union, Iterable, Optional from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict @@ -9,6 +10,24 @@ __all__ = [ "ProjectValidateParams", + "Response", + "ResponseChatCompletion", + "ResponseChatCompletionChoice", + "ResponseChatCompletionChoiceMessage", + "ResponseChatCompletionChoiceMessageAnnotation", + "ResponseChatCompletionChoiceMessageAnnotationURLCitation", + "ResponseChatCompletionChoiceMessageAudio", + "ResponseChatCompletionChoiceMessageFunctionCall", + "ResponseChatCompletionChoiceMessageToolCall", + "ResponseChatCompletionChoiceMessageToolCallFunction", + "ResponseChatCompletionChoiceLogprobs", + "ResponseChatCompletionChoiceLogprobsContent", + "ResponseChatCompletionChoiceLogprobsContentTopLogprob", + "ResponseChatCompletionChoiceLogprobsRefusal", + "ResponseChatCompletionChoiceLogprobsRefusalTopLogprob", + "ResponseChatCompletionUsage", + "ResponseChatCompletionUsageCompletionTokensDetails", + "ResponseChatCompletionUsagePromptTokensDetails", "Message", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", @@ -41,11 +60,9 @@ class ProjectValidateParams(TypedDict, total=False): context: Required[str] - prompt: Required[str] - query: Required[str] - response: Required[str] + response: Required[Response] use_llm_matching: bool @@ -66,11 +83,10 @@ class ProjectValidateParams(TypedDict, total=False): If not provided, TLM will be used to generate scores. """ - messages: Optional[Iterable[Message]] - """Optional message history to provide conversation context for the query. + messages: Iterable[Message] + """Message history to provide conversation context for the query. - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. + Messages contain up to and including the latest user prompt to the LLM. """ options: Optional[Options] @@ -161,9 +177,21 @@ class ProjectValidateParams(TypedDict, total=False): - criteria: Instructions specifying the evaluation criteria. """ + prompt: Optional[str] + """The prompt to use for the TLM call. + + If not provided, the prompt will be generated from the messages. + """ + quality_preset: Literal["best", "high", "medium", "low", "base"] """The quality preset to use for the TLM or Trustworthy RAG API.""" + rewritten_question: Optional[str] + """ + The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + """ + task: Optional[str] x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] @@ -175,6 +203,243 @@ class ProjectValidateParams(TypedDict, total=False): x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] +class ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped(TypedDict, total=False): + end_index: Required[int] + + start_index: Required[int] + + title: Required[str] + + url: Required[str] + + +ResponseChatCompletionChoiceMessageAnnotationURLCitation: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageAnnotationTyped(TypedDict, total=False): + type: Required[Literal["url_citation"]] + + url_citation: Required[ResponseChatCompletionChoiceMessageAnnotationURLCitation] + + +ResponseChatCompletionChoiceMessageAnnotation: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAnnotationTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageAudioTyped(TypedDict, total=False): + id: Required[str] + + data: Required[str] + + expires_at: Required[int] + + transcript: Required[str] + + +ResponseChatCompletionChoiceMessageAudio: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAudioTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageFunctionCallTyped(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +ResponseChatCompletionChoiceMessageFunctionCall: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageFunctionCallTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageToolCallFunctionTyped(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +ResponseChatCompletionChoiceMessageToolCallFunction: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageToolCallFunctionTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageToolCallTyped(TypedDict, total=False): + id: Required[str] + + function: Required[ResponseChatCompletionChoiceMessageToolCallFunction] + + type: Required[Literal["function"]] + + +ResponseChatCompletionChoiceMessageToolCall: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageToolCallTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageTyped(TypedDict, total=False): + role: Required[Literal["assistant"]] + + annotations: Optional[Iterable[ResponseChatCompletionChoiceMessageAnnotation]] + + audio: Optional[ResponseChatCompletionChoiceMessageAudio] + + content: Optional[str] + + function_call: Optional[ResponseChatCompletionChoiceMessageFunctionCall] + + refusal: Optional[str] + + tool_calls: Optional[Iterable[ResponseChatCompletionChoiceMessageToolCall]] + + +ResponseChatCompletionChoiceMessage: TypeAlias = Union[ResponseChatCompletionChoiceMessageTyped, Dict[str, object]] + + +class ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsContentTopLogprob: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsContentTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsContentTopLogprob]] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsContent: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsContentTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsRefusalTopLogprob: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsRefusalTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsRefusalTopLogprob]] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsRefusal: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsRefusalTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsTyped(TypedDict, total=False): + content: Optional[Iterable[ResponseChatCompletionChoiceLogprobsContent]] + + refusal: Optional[Iterable[ResponseChatCompletionChoiceLogprobsRefusal]] + + +ResponseChatCompletionChoiceLogprobs: TypeAlias = Union[ResponseChatCompletionChoiceLogprobsTyped, Dict[str, object]] + + +class ResponseChatCompletionChoiceTyped(TypedDict, total=False): + finish_reason: Required[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] + + index: Required[int] + + message: Required[ResponseChatCompletionChoiceMessage] + + logprobs: Optional[ResponseChatCompletionChoiceLogprobs] + + +ResponseChatCompletionChoice: TypeAlias = Union[ResponseChatCompletionChoiceTyped, Dict[str, object]] + + +class ResponseChatCompletionUsageCompletionTokensDetailsTyped(TypedDict, total=False): + accepted_prediction_tokens: Optional[int] + + audio_tokens: Optional[int] + + reasoning_tokens: Optional[int] + + rejected_prediction_tokens: Optional[int] + + +ResponseChatCompletionUsageCompletionTokensDetails: TypeAlias = Union[ + ResponseChatCompletionUsageCompletionTokensDetailsTyped, Dict[str, object] +] + + +class ResponseChatCompletionUsagePromptTokensDetailsTyped(TypedDict, total=False): + audio_tokens: Optional[int] + + cached_tokens: Optional[int] + + +ResponseChatCompletionUsagePromptTokensDetails: TypeAlias = Union[ + ResponseChatCompletionUsagePromptTokensDetailsTyped, Dict[str, object] +] + + +class ResponseChatCompletionUsageTyped(TypedDict, total=False): + completion_tokens: Required[int] + + prompt_tokens: Required[int] + + total_tokens: Required[int] + + completion_tokens_details: Optional[ResponseChatCompletionUsageCompletionTokensDetails] + + prompt_tokens_details: Optional[ResponseChatCompletionUsagePromptTokensDetails] + + +ResponseChatCompletionUsage: TypeAlias = Union[ResponseChatCompletionUsageTyped, Dict[str, object]] + + +class ResponseChatCompletionTyped(TypedDict, total=False): + id: Required[str] + + choices: Required[Iterable[ResponseChatCompletionChoice]] + + created: Required[int] + + model: Required[str] + + object: Required[Literal["chat.completion"]] + + service_tier: Optional[Literal["scale", "default"]] + + system_fingerprint: Optional[str] + + usage: Optional[ResponseChatCompletionUsage] + + +ResponseChatCompletion: TypeAlias = Union[ResponseChatCompletionTyped, Dict[str, builtins.object]] + +Response: TypeAlias = Union[str, ResponseChatCompletion] + + class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): text: Required[str] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 0764d9a1..9ecffa09 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -632,9 +632,8 @@ def test_method_validate(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -644,9 +643,8 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", use_llm_matching=True, constrain_outputs=["string"], custom_eval_thresholds={"foo": 0}, @@ -670,7 +668,9 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: "similarity_measure": "similarity_measure", "use_self_reflection": True, }, + prompt="prompt", quality_preset="best", + rewritten_question="rewritten_question", task="task", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -685,9 +685,8 @@ def test_raw_response_validate(self, client: Codex) -> None: response = client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert response.is_closed is True @@ -701,9 +700,8 @@ def test_streaming_response_validate(self, client: Codex) -> None: with client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -720,9 +718,8 @@ def test_path_params_validate(self, client: Codex) -> None: client.projects.with_raw_response.validate( project_id="", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) @@ -1337,9 +1334,8 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None: project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -1349,9 +1345,8 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", use_llm_matching=True, constrain_outputs=["string"], custom_eval_thresholds={"foo": 0}, @@ -1375,7 +1370,9 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - "similarity_measure": "similarity_measure", "use_self_reflection": True, }, + prompt="prompt", quality_preset="best", + rewritten_question="rewritten_question", task="task", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -1390,9 +1387,8 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert response.is_closed is True @@ -1406,9 +1402,8 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No async with async_client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1425,7 +1420,6 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None: await async_client.projects.with_raw_response.validate( project_id="", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) From 58f4575d7f26720829ceccb74f4bcb86ff7a30af Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:38:16 +0000 Subject: [PATCH 190/320] feat(api): remove entries routes --- .stats.yml | 4 +- src/codex/pagination.py | 62 ----- src/codex/resources/projects/projects.py | 42 ++-- src/codex/types/project_validate_params.py | 279 +-------------------- tests/api_resources/test_projects.py | 34 +-- 5 files changed, 43 insertions(+), 378 deletions(-) diff --git a/.stats.yml b/.stats.yml index c8908e83..d7fdc0e2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09 -config_hash: 14b2643a0ec60cf326dfed00939644ff +openapi_spec_hash: f17890d85522687a4c68702da9ad2efb +config_hash: e62d723b4a5c564dc05390753a876f31 diff --git a/src/codex/pagination.py b/src/codex/pagination.py index 1f590df5..8057f2e8 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -14,8 +14,6 @@ "AsyncMyOffsetPageTopLevelArray", "SyncOffsetPageClusters", "AsyncOffsetPageClusters", - "SyncOffsetPageEntries", - "AsyncOffsetPageEntries", ] _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) @@ -143,63 +141,3 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) return None - - -class SyncOffsetPageEntries(BaseSyncPage[_T], BasePage[_T], Generic[_T]): - entries: List[_T] - total_count: Optional[int] = None - - @override - def _get_page_items(self) -> List[_T]: - entries = self.entries - if not entries: - return [] - return entries - - @override - def next_page_info(self) -> Optional[PageInfo]: - offset = self._options.params.get("offset") or 0 - if not isinstance(offset, int): - raise ValueError(f'Expected "offset" param to be an integer but got {offset}') - - length = len(self._get_page_items()) - current_count = offset + length - - total_count = self.total_count - if total_count is None: - return None - - if current_count < total_count: - return PageInfo(params={"offset": current_count}) - - return None - - -class AsyncOffsetPageEntries(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): - entries: List[_T] - total_count: Optional[int] = None - - @override - def _get_page_items(self) -> List[_T]: - entries = self.entries - if not entries: - return [] - return entries - - @override - def next_page_info(self) -> Optional[PageInfo]: - offset = self._options.params.get("offset") or 0 - if not isinstance(offset, int): - raise ValueError(f'Expected "offset" param to be an integer but got {offset}') - - length = len(self._get_page_items()) - current_count = offset + length - - total_count = self.total_count - if total_count is None: - return None - - if current_count < total_count: - return PageInfo(params={"offset": current_count}) - - return None diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index dc01b112..1314b7be 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -488,18 +488,17 @@ def validate( project_id: str, *, context: str, + prompt: str, query: str, - response: project_validate_params.Response, + response: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, + messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, - prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -527,8 +526,9 @@ def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. - messages: Message history to provide conversation context for the query. Messages contain - up to and including the latest user prompt to the LLM. + messages: Optional message history to provide conversation context for the query. Used to + rewrite query into a self-contained version of itself. If not provided, the + query will be treated as self-contained. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -615,14 +615,8 @@ def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - prompt: The prompt to use for the TLM call. If not provided, the prompt will be - generated from the messages. - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. - rewritten_question: The re-written query if it was provided by the client to Codex from a user to be - used instead of the original query. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -649,6 +643,7 @@ def validate( body=maybe_transform( { "context": context, + "prompt": prompt, "query": query, "response": response, "constrain_outputs": constrain_outputs, @@ -657,9 +652,7 @@ def validate( "eval_scores": eval_scores, "messages": messages, "options": options, - "prompt": prompt, "quality_preset": quality_preset, - "rewritten_question": rewritten_question, "task": task, }, project_validate_params.ProjectValidateParams, @@ -1097,18 +1090,17 @@ async def validate( project_id: str, *, context: str, + prompt: str, query: str, - response: project_validate_params.Response, + response: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, + messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, - prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -1136,8 +1128,9 @@ async def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. - messages: Message history to provide conversation context for the query. Messages contain - up to and including the latest user prompt to the LLM. + messages: Optional message history to provide conversation context for the query. Used to + rewrite query into a self-contained version of itself. If not provided, the + query will be treated as self-contained. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -1224,14 +1217,8 @@ async def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - prompt: The prompt to use for the TLM call. If not provided, the prompt will be - generated from the messages. - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. - rewritten_question: The re-written query if it was provided by the client to Codex from a user to be - used instead of the original query. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1258,6 +1245,7 @@ async def validate( body=await async_maybe_transform( { "context": context, + "prompt": prompt, "query": query, "response": response, "constrain_outputs": constrain_outputs, @@ -1266,9 +1254,7 @@ async def validate( "eval_scores": eval_scores, "messages": messages, "options": options, - "prompt": prompt, "quality_preset": quality_preset, - "rewritten_question": rewritten_question, "task": task, }, project_validate_params.ProjectValidateParams, diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 0862cbc1..8b38ebfa 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -2,7 +2,6 @@ from __future__ import annotations -import builtins from typing import Dict, List, Union, Iterable, Optional from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict @@ -10,24 +9,6 @@ __all__ = [ "ProjectValidateParams", - "Response", - "ResponseChatCompletion", - "ResponseChatCompletionChoice", - "ResponseChatCompletionChoiceMessage", - "ResponseChatCompletionChoiceMessageAnnotation", - "ResponseChatCompletionChoiceMessageAnnotationURLCitation", - "ResponseChatCompletionChoiceMessageAudio", - "ResponseChatCompletionChoiceMessageFunctionCall", - "ResponseChatCompletionChoiceMessageToolCall", - "ResponseChatCompletionChoiceMessageToolCallFunction", - "ResponseChatCompletionChoiceLogprobs", - "ResponseChatCompletionChoiceLogprobsContent", - "ResponseChatCompletionChoiceLogprobsContentTopLogprob", - "ResponseChatCompletionChoiceLogprobsRefusal", - "ResponseChatCompletionChoiceLogprobsRefusalTopLogprob", - "ResponseChatCompletionUsage", - "ResponseChatCompletionUsageCompletionTokensDetails", - "ResponseChatCompletionUsagePromptTokensDetails", "Message", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", @@ -60,9 +41,11 @@ class ProjectValidateParams(TypedDict, total=False): context: Required[str] + prompt: Required[str] + query: Required[str] - response: Required[Response] + response: Required[str] use_llm_matching: bool @@ -83,10 +66,11 @@ class ProjectValidateParams(TypedDict, total=False): If not provided, TLM will be used to generate scores. """ - messages: Iterable[Message] - """Message history to provide conversation context for the query. + messages: Optional[Iterable[Message]] + """Optional message history to provide conversation context for the query. - Messages contain up to and including the latest user prompt to the LLM. + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. """ options: Optional[Options] @@ -177,21 +161,9 @@ class ProjectValidateParams(TypedDict, total=False): - criteria: Instructions specifying the evaluation criteria. """ - prompt: Optional[str] - """The prompt to use for the TLM call. - - If not provided, the prompt will be generated from the messages. - """ - quality_preset: Literal["best", "high", "medium", "low", "base"] """The quality preset to use for the TLM or Trustworthy RAG API.""" - rewritten_question: Optional[str] - """ - The re-written query if it was provided by the client to Codex from a user to be - used instead of the original query. - """ - task: Optional[str] x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] @@ -203,243 +175,6 @@ class ProjectValidateParams(TypedDict, total=False): x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] -class ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped(TypedDict, total=False): - end_index: Required[int] - - start_index: Required[int] - - title: Required[str] - - url: Required[str] - - -ResponseChatCompletionChoiceMessageAnnotationURLCitation: TypeAlias = Union[ - ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceMessageAnnotationTyped(TypedDict, total=False): - type: Required[Literal["url_citation"]] - - url_citation: Required[ResponseChatCompletionChoiceMessageAnnotationURLCitation] - - -ResponseChatCompletionChoiceMessageAnnotation: TypeAlias = Union[ - ResponseChatCompletionChoiceMessageAnnotationTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceMessageAudioTyped(TypedDict, total=False): - id: Required[str] - - data: Required[str] - - expires_at: Required[int] - - transcript: Required[str] - - -ResponseChatCompletionChoiceMessageAudio: TypeAlias = Union[ - ResponseChatCompletionChoiceMessageAudioTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceMessageFunctionCallTyped(TypedDict, total=False): - arguments: Required[str] - - name: Required[str] - - -ResponseChatCompletionChoiceMessageFunctionCall: TypeAlias = Union[ - ResponseChatCompletionChoiceMessageFunctionCallTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceMessageToolCallFunctionTyped(TypedDict, total=False): - arguments: Required[str] - - name: Required[str] - - -ResponseChatCompletionChoiceMessageToolCallFunction: TypeAlias = Union[ - ResponseChatCompletionChoiceMessageToolCallFunctionTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceMessageToolCallTyped(TypedDict, total=False): - id: Required[str] - - function: Required[ResponseChatCompletionChoiceMessageToolCallFunction] - - type: Required[Literal["function"]] - - -ResponseChatCompletionChoiceMessageToolCall: TypeAlias = Union[ - ResponseChatCompletionChoiceMessageToolCallTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceMessageTyped(TypedDict, total=False): - role: Required[Literal["assistant"]] - - annotations: Optional[Iterable[ResponseChatCompletionChoiceMessageAnnotation]] - - audio: Optional[ResponseChatCompletionChoiceMessageAudio] - - content: Optional[str] - - function_call: Optional[ResponseChatCompletionChoiceMessageFunctionCall] - - refusal: Optional[str] - - tool_calls: Optional[Iterable[ResponseChatCompletionChoiceMessageToolCall]] - - -ResponseChatCompletionChoiceMessage: TypeAlias = Union[ResponseChatCompletionChoiceMessageTyped, Dict[str, object]] - - -class ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped(TypedDict, total=False): - token: Required[str] - - logprob: Required[float] - - bytes: Optional[Iterable[int]] - - -ResponseChatCompletionChoiceLogprobsContentTopLogprob: TypeAlias = Union[ - ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceLogprobsContentTyped(TypedDict, total=False): - token: Required[str] - - logprob: Required[float] - - top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsContentTopLogprob]] - - bytes: Optional[Iterable[int]] - - -ResponseChatCompletionChoiceLogprobsContent: TypeAlias = Union[ - ResponseChatCompletionChoiceLogprobsContentTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped(TypedDict, total=False): - token: Required[str] - - logprob: Required[float] - - bytes: Optional[Iterable[int]] - - -ResponseChatCompletionChoiceLogprobsRefusalTopLogprob: TypeAlias = Union[ - ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceLogprobsRefusalTyped(TypedDict, total=False): - token: Required[str] - - logprob: Required[float] - - top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsRefusalTopLogprob]] - - bytes: Optional[Iterable[int]] - - -ResponseChatCompletionChoiceLogprobsRefusal: TypeAlias = Union[ - ResponseChatCompletionChoiceLogprobsRefusalTyped, Dict[str, object] -] - - -class ResponseChatCompletionChoiceLogprobsTyped(TypedDict, total=False): - content: Optional[Iterable[ResponseChatCompletionChoiceLogprobsContent]] - - refusal: Optional[Iterable[ResponseChatCompletionChoiceLogprobsRefusal]] - - -ResponseChatCompletionChoiceLogprobs: TypeAlias = Union[ResponseChatCompletionChoiceLogprobsTyped, Dict[str, object]] - - -class ResponseChatCompletionChoiceTyped(TypedDict, total=False): - finish_reason: Required[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] - - index: Required[int] - - message: Required[ResponseChatCompletionChoiceMessage] - - logprobs: Optional[ResponseChatCompletionChoiceLogprobs] - - -ResponseChatCompletionChoice: TypeAlias = Union[ResponseChatCompletionChoiceTyped, Dict[str, object]] - - -class ResponseChatCompletionUsageCompletionTokensDetailsTyped(TypedDict, total=False): - accepted_prediction_tokens: Optional[int] - - audio_tokens: Optional[int] - - reasoning_tokens: Optional[int] - - rejected_prediction_tokens: Optional[int] - - -ResponseChatCompletionUsageCompletionTokensDetails: TypeAlias = Union[ - ResponseChatCompletionUsageCompletionTokensDetailsTyped, Dict[str, object] -] - - -class ResponseChatCompletionUsagePromptTokensDetailsTyped(TypedDict, total=False): - audio_tokens: Optional[int] - - cached_tokens: Optional[int] - - -ResponseChatCompletionUsagePromptTokensDetails: TypeAlias = Union[ - ResponseChatCompletionUsagePromptTokensDetailsTyped, Dict[str, object] -] - - -class ResponseChatCompletionUsageTyped(TypedDict, total=False): - completion_tokens: Required[int] - - prompt_tokens: Required[int] - - total_tokens: Required[int] - - completion_tokens_details: Optional[ResponseChatCompletionUsageCompletionTokensDetails] - - prompt_tokens_details: Optional[ResponseChatCompletionUsagePromptTokensDetails] - - -ResponseChatCompletionUsage: TypeAlias = Union[ResponseChatCompletionUsageTyped, Dict[str, object]] - - -class ResponseChatCompletionTyped(TypedDict, total=False): - id: Required[str] - - choices: Required[Iterable[ResponseChatCompletionChoice]] - - created: Required[int] - - model: Required[str] - - object: Required[Literal["chat.completion"]] - - service_tier: Optional[Literal["scale", "default"]] - - system_fingerprint: Optional[str] - - usage: Optional[ResponseChatCompletionUsage] - - -ResponseChatCompletion: TypeAlias = Union[ResponseChatCompletionTyped, Dict[str, builtins.object]] - -Response: TypeAlias = Union[str, ResponseChatCompletion] - - class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): text: Required[str] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 9ecffa09..0764d9a1 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -632,8 +632,9 @@ def test_method_validate(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -643,8 +644,9 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", use_llm_matching=True, constrain_outputs=["string"], custom_eval_thresholds={"foo": 0}, @@ -668,9 +670,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: "similarity_measure": "similarity_measure", "use_self_reflection": True, }, - prompt="prompt", quality_preset="best", - rewritten_question="rewritten_question", task="task", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -685,8 +685,9 @@ def test_raw_response_validate(self, client: Codex) -> None: response = client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) assert response.is_closed is True @@ -700,8 +701,9 @@ def test_streaming_response_validate(self, client: Codex) -> None: with client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -718,8 +720,9 @@ def test_path_params_validate(self, client: Codex) -> None: client.projects.with_raw_response.validate( project_id="", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) @@ -1334,8 +1337,9 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None: project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -1345,8 +1349,9 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", use_llm_matching=True, constrain_outputs=["string"], custom_eval_thresholds={"foo": 0}, @@ -1370,9 +1375,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - "similarity_measure": "similarity_measure", "use_self_reflection": True, }, - prompt="prompt", quality_preset="best", - rewritten_question="rewritten_question", task="task", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -1387,8 +1390,9 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) assert response.is_closed is True @@ -1402,8 +1406,9 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No async with async_client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1420,6 +1425,7 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None: await async_client.projects.with_raw_response.validate( project_id="", context="context", + prompt="prompt", query="query", - response="string", + response="response", ) From cb9e15a9954e2383e19ca53ba704091c3ed51c6e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:39:41 +0000 Subject: [PATCH 191/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d7fdc0e2..429b3f80 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 openapi_spec_hash: f17890d85522687a4c68702da9ad2efb -config_hash: e62d723b4a5c564dc05390753a876f31 +config_hash: f44faee9ff5bdf66e9e38fc69f6e4daa From edbf9290a2a47d6975b16d84423e0eef7961b045 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:42:15 +0000 Subject: [PATCH 192/320] feat(api): define pagination schemes --- .stats.yml | 2 +- README.md | 69 ++++++++++ api.md | 4 +- src/codex/pagination.py | 124 ++++++++++++++++++ src/codex/resources/projects/query_logs.py | 21 +-- src/codex/resources/projects/remediations.py | 21 +-- .../types/projects/query_log_list_response.py | 47 +++---- .../projects/remediation_list_response.py | 12 +- .../api_resources/projects/test_query_logs.py | 17 +-- .../projects/test_remediations.py | 17 +-- 10 files changed, 260 insertions(+), 74 deletions(-) diff --git a/.stats.yml b/.stats.yml index 429b3f80..ac24d567 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 openapi_spec_hash: f17890d85522687a4c68702da9ad2efb -config_hash: f44faee9ff5bdf66e9e38fc69f6e4daa +config_hash: 86582a50eb22b2866777cbd4d94f4e8d diff --git a/README.md b/README.md index 756f6d69..ae3be8a6 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,75 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## Pagination + +List methods in the Codex API are paginated. + +This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually: + +```python +from codex import Codex + +client = Codex() + +all_query_logs = [] +# Automatically fetches more pages as needed. +for query_log in client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +): + # Do something with query_log here + all_query_logs.append(query_log) +print(all_query_logs) +``` + +Or, asynchronously: + +```python +import asyncio +from codex import AsyncCodex + +client = AsyncCodex() + + +async def main() -> None: + all_query_logs = [] + # Iterate through items across all pages, issuing requests as needed. + async for query_log in client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ): + all_query_logs.append(query_log) + print(all_query_logs) + + +asyncio.run(main()) +``` + +Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: + +```python +first_page = await client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) +if first_page.has_next_page(): + print(f"will fetch next page using these details: {first_page.next_page_info()}") + next_page = await first_page.get_next_page() + print(f"number of items we just fetched: {len(next_page.query_logs)}") + +# Remove `await` for non-async usage. +``` + +Or just work directly with the returned data: + +```python +first_page = await client.projects.query_logs.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", +) +for query_log in first_page.query_logs: + print(query_log.id) + +# Remove `await` for non-async usage. +``` + ## Nested params Nested parameters are dictionaries, typed using `TypedDict`, for example: diff --git a/api.md b/api.md index ba4dfd63..edc03cd3 100644 --- a/api.md +++ b/api.md @@ -212,7 +212,7 @@ from codex.types.projects import ( Methods: - client.projects.query_logs.retrieve(query_log_id, \*, project_id) -> QueryLogRetrieveResponse -- client.projects.query_logs.list(project_id, \*\*params) -> QueryLogListResponse +- client.projects.query_logs.list(project_id, \*\*params) -> SyncOffsetPageQueryLogs[QueryLogListResponse] - client.projects.query_logs.list_by_group(project_id, \*\*params) -> QueryLogListByGroupResponse - client.projects.query_logs.list_groups(project_id, \*\*params) -> QueryLogListGroupsResponse - client.projects.query_logs.start_remediation(query_log_id, \*, project_id) -> QueryLogStartRemediationResponse @@ -240,7 +240,7 @@ Methods: - client.projects.remediations.create(project_id, \*\*params) -> RemediationCreateResponse - client.projects.remediations.retrieve(remediation_id, \*, project_id) -> RemediationRetrieveResponse -- client.projects.remediations.list(project_id, \*\*params) -> RemediationListResponse +- client.projects.remediations.list(project_id, \*\*params) -> SyncOffsetPageRemediations[RemediationListResponse] - client.projects.remediations.delete(remediation_id, \*, project_id) -> None - client.projects.remediations.edit_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditAnswerResponse - client.projects.remediations.edit_draft_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditDraftAnswerResponse diff --git a/src/codex/pagination.py b/src/codex/pagination.py index 8057f2e8..89ed0cfb 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -14,6 +14,10 @@ "AsyncMyOffsetPageTopLevelArray", "SyncOffsetPageClusters", "AsyncOffsetPageClusters", + "SyncOffsetPageQueryLogs", + "AsyncOffsetPageQueryLogs", + "SyncOffsetPageRemediations", + "AsyncOffsetPageRemediations", ] _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) @@ -141,3 +145,123 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) return None + + +class SyncOffsetPageQueryLogs(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + query_logs: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + query_logs = self.query_logs + if not query_logs: + return [] + return query_logs + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncOffsetPageQueryLogs(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + query_logs: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + query_logs = self.query_logs + if not query_logs: + return [] + return query_logs + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class SyncOffsetPageRemediations(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + remediations: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + remediations = self.remediations + if not remediations: + return [] + return remediations + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncOffsetPageRemediations(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + remediations: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + remediations = self.remediations + if not remediations: + return [] + return remediations + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 148df9f6..24535d4b 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -18,7 +18,8 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._base_client import make_request_options +from ...pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs +from ..._base_client import AsyncPaginator, make_request_options from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params from ...types.projects.query_log_list_response import QueryLogListResponse from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse @@ -110,7 +111,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> QueryLogListResponse: + ) -> SyncOffsetPageQueryLogs[QueryLogListResponse]: """ List query logs by project ID. @@ -141,8 +142,9 @@ def list( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._get( + return self._get_api_list( f"/api/projects/{project_id}/query_logs/", + page=SyncOffsetPageQueryLogs[QueryLogListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -166,7 +168,7 @@ def list( query_log_list_params.QueryLogListParams, ), ), - cast_to=QueryLogListResponse, + model=QueryLogListResponse, ) def list_by_group( @@ -443,7 +445,7 @@ async def retrieve( cast_to=QueryLogRetrieveResponse, ) - async def list( + def list( self, project_id: str, *, @@ -468,7 +470,7 @@ async def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> QueryLogListResponse: + ) -> AsyncPaginator[QueryLogListResponse, AsyncOffsetPageQueryLogs[QueryLogListResponse]]: """ List query logs by project ID. @@ -499,14 +501,15 @@ async def list( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return await self._get( + return self._get_api_list( f"/api/projects/{project_id}/query_logs/", + page=AsyncOffsetPageQueryLogs[QueryLogListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( + query=maybe_transform( { "created_at_end": created_at_end, "created_at_start": created_at_start, @@ -524,7 +527,7 @@ async def list( query_log_list_params.QueryLogListParams, ), ), - cast_to=QueryLogListResponse, + model=QueryLogListResponse, ) async def list_by_group( diff --git a/src/codex/resources/projects/remediations.py b/src/codex/resources/projects/remediations.py index 65015a14..ea0168c7 100644 --- a/src/codex/resources/projects/remediations.py +++ b/src/codex/resources/projects/remediations.py @@ -18,7 +18,8 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._base_client import make_request_options +from ...pagination import SyncOffsetPageRemediations, AsyncOffsetPageRemediations +from ..._base_client import AsyncPaginator, make_request_options from ...types.projects import ( remediation_list_params, remediation_create_params, @@ -159,7 +160,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> RemediationListResponse: + ) -> SyncOffsetPageRemediations[RemediationListResponse]: """ List remediations by project ID. @@ -186,8 +187,9 @@ def list( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._get( + return self._get_api_list( f"/api/projects/{project_id}/remediations/", + page=SyncOffsetPageRemediations[RemediationListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -209,7 +211,7 @@ def list( remediation_list_params.RemediationListParams, ), ), - cast_to=RemediationListResponse, + model=RemediationListResponse, ) def delete( @@ -608,7 +610,7 @@ async def retrieve( cast_to=RemediationRetrieveResponse, ) - async def list( + def list( self, project_id: str, *, @@ -628,7 +630,7 @@ async def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> RemediationListResponse: + ) -> AsyncPaginator[RemediationListResponse, AsyncOffsetPageRemediations[RemediationListResponse]]: """ List remediations by project ID. @@ -655,14 +657,15 @@ async def list( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return await self._get( + return self._get_api_list( f"/api/projects/{project_id}/remediations/", + page=AsyncOffsetPageRemediations[RemediationListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( + query=maybe_transform( { "created_at_end": created_at_end, "created_at_start": created_at_start, @@ -678,7 +681,7 @@ async def list( remediation_list_params.RemediationListParams, ), ), - cast_to=RemediationListResponse, + model=RemediationListResponse, ) async def delete( diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 147478cf..ccdeb038 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -8,41 +8,40 @@ __all__ = [ "QueryLogListResponse", - "QueryLog", - "QueryLogFormattedEscalationEvalScores", - "QueryLogFormattedEvalScores", - "QueryLogFormattedGuardrailEvalScores", - "QueryLogFormattedNonGuardrailEvalScores", - "QueryLogContext", - "QueryLogDeterministicGuardrailsResults", + "FormattedEscalationEvalScores", + "FormattedEvalScores", + "FormattedGuardrailEvalScores", + "FormattedNonGuardrailEvalScores", + "Context", + "DeterministicGuardrailsResults", ] -class QueryLogFormattedEscalationEvalScores(BaseModel): +class FormattedEscalationEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogFormattedEvalScores(BaseModel): +class FormattedEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogFormattedGuardrailEvalScores(BaseModel): +class FormattedGuardrailEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogFormattedNonGuardrailEvalScores(BaseModel): +class FormattedNonGuardrailEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogContext(BaseModel): +class Context(BaseModel): content: str """The actual content/text of the document.""" @@ -59,7 +58,7 @@ class QueryLogContext(BaseModel): """Title or heading of the document. Useful for display and context.""" -class QueryLogDeterministicGuardrailsResults(BaseModel): +class DeterministicGuardrailsResults(BaseModel): guardrail_name: str should_guardrail: bool @@ -67,14 +66,14 @@ class QueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class QueryLog(BaseModel): +class QueryLogListResponse(BaseModel): id: str created_at: datetime - formatted_escalation_eval_scores: Optional[Dict[str, QueryLogFormattedEscalationEvalScores]] = None + formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None - formatted_eval_scores: Optional[Dict[str, QueryLogFormattedEvalScores]] = None + formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None """Format evaluation scores for frontend display with pass/fail status. Returns: Dictionary mapping eval keys to their formatted representation: { @@ -82,9 +81,9 @@ class QueryLog(BaseModel): eval_scores is None. """ - formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None + formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None - formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None is_bad_response: bool @@ -97,7 +96,7 @@ class QueryLog(BaseModel): was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" - context: Optional[List[QueryLogContext]] = None + context: Optional[List[Context]] = None """RAG context used for the query""" custom_metadata: Optional[object] = None @@ -106,7 +105,7 @@ class QueryLog(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" - deterministic_guardrails_results: Optional[Dict[str, QueryLogDeterministicGuardrailsResults]] = None + deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None """Results of deterministic guardrails applied to the query""" escalated: Optional[bool] = None @@ -138,11 +137,3 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - - -class QueryLogListResponse(BaseModel): - custom_metadata_columns: List[str] - - query_logs: List[QueryLog] - - total_count: int diff --git a/src/codex/types/projects/remediation_list_response.py b/src/codex/types/projects/remediation_list_response.py index 9d5cda86..83410539 100644 --- a/src/codex/types/projects/remediation_list_response.py +++ b/src/codex/types/projects/remediation_list_response.py @@ -1,15 +1,15 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Optional from datetime import datetime from typing_extensions import Literal from ..._models import BaseModel -__all__ = ["RemediationListResponse", "Remediation"] +__all__ = ["RemediationListResponse"] -class Remediation(BaseModel): +class RemediationListResponse(BaseModel): id: str answered_at: Optional[datetime] = None @@ -35,9 +35,3 @@ class Remediation(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None - - -class RemediationListResponse(BaseModel): - remediations: List[Remediation] - - total_count: int diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 68d78ced..be867e22 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -10,6 +10,7 @@ from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex._utils import parse_datetime +from codex.pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs from codex.types.projects import ( QueryLogListResponse, QueryLogRetrieveResponse, @@ -82,7 +83,7 @@ def test_method_list(self, client: Codex) -> None: query_log = client.projects.query_logs.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -102,7 +103,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: sort="created_at", was_cache_hit=True, ) - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -114,7 +115,7 @@ def test_raw_response_list(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = response.parse() - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -126,7 +127,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = response.parse() - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) assert cast(Any, response.is_closed) is True @@ -381,7 +382,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -401,7 +402,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No sort="created_at", was_cache_hit=True, ) - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -413,7 +414,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = await response.parse() - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -425,7 +426,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = await response.parse() - assert_matches_type(QueryLogListResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py index c75a255c..947850f7 100644 --- a/tests/api_resources/projects/test_remediations.py +++ b/tests/api_resources/projects/test_remediations.py @@ -10,6 +10,7 @@ from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex._utils import parse_datetime +from codex.pagination import SyncOffsetPageRemediations, AsyncOffsetPageRemediations from codex.types.projects import ( RemediationListResponse, RemediationPauseResponse, @@ -144,7 +145,7 @@ def test_method_list(self, client: Codex) -> None: remediation = client.projects.remediations.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip() @parametrize @@ -162,7 +163,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: sort="created_at", status=["ACTIVE"], ) - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip() @parametrize @@ -174,7 +175,7 @@ def test_raw_response_list(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" remediation = response.parse() - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip() @parametrize @@ -186,7 +187,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" remediation = response.parse() - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) assert cast(Any, response.is_closed) is True @@ -745,7 +746,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.list( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip() @parametrize @@ -763,7 +764,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No sort="created_at", status=["ACTIVE"], ) - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip() @parametrize @@ -775,7 +776,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" remediation = await response.parse() - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip() @parametrize @@ -787,7 +788,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" remediation = await response.parse() - assert_matches_type(RemediationListResponse, remediation, path=["response"]) + assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) assert cast(Any, response.is_closed) is True From 4f67d97b8700ec1d8fe800f2a943b314d2011bcc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:43:46 +0000 Subject: [PATCH 193/320] feat(api): more pagination schemes --- .stats.yml | 2 +- api.md | 2 +- src/codex/pagination.py | 124 ++++++++++++++++++ src/codex/resources/projects/query_logs.py | 25 ++-- .../query_log_list_groups_response.py | 47 +++---- .../api_resources/projects/test_query_logs.py | 23 ++-- 6 files changed, 175 insertions(+), 48 deletions(-) diff --git a/.stats.yml b/.stats.yml index ac24d567..01dc7b27 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 openapi_spec_hash: f17890d85522687a4c68702da9ad2efb -config_hash: 86582a50eb22b2866777cbd4d94f4e8d +config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/api.md b/api.md index edc03cd3..0c027913 100644 --- a/api.md +++ b/api.md @@ -214,7 +214,7 @@ Methods: - client.projects.query_logs.retrieve(query_log_id, \*, project_id) -> QueryLogRetrieveResponse - client.projects.query_logs.list(project_id, \*\*params) -> SyncOffsetPageQueryLogs[QueryLogListResponse] - client.projects.query_logs.list_by_group(project_id, \*\*params) -> QueryLogListByGroupResponse -- client.projects.query_logs.list_groups(project_id, \*\*params) -> QueryLogListGroupsResponse +- client.projects.query_logs.list_groups(project_id, \*\*params) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse] - client.projects.query_logs.start_remediation(query_log_id, \*, project_id) -> QueryLogStartRemediationResponse ## Remediations diff --git a/src/codex/pagination.py b/src/codex/pagination.py index 89ed0cfb..36af0f63 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -18,6 +18,10 @@ "AsyncOffsetPageQueryLogs", "SyncOffsetPageRemediations", "AsyncOffsetPageRemediations", + "SyncOffsetPageQueryLogGroups", + "AsyncOffsetPageQueryLogGroups", + "SyncOffsetPageQueryLogsByGroup", + "AsyncOffsetPageQueryLogsByGroup", ] _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) @@ -265,3 +269,123 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) return None + + +class SyncOffsetPageQueryLogGroups(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + query_log_groups: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + query_log_groups = self.query_log_groups + if not query_log_groups: + return [] + return query_log_groups + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncOffsetPageQueryLogGroups(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + query_log_groups: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + query_log_groups = self.query_log_groups + if not query_log_groups: + return [] + return query_log_groups + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class SyncOffsetPageQueryLogsByGroup(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + query_logs_by_group: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + query_logs_by_group = self.query_logs_by_group + if not query_logs_by_group: + return [] + return query_logs_by_group + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncOffsetPageQueryLogsByGroup(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + query_logs_by_group: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + query_logs_by_group = self.query_logs_by_group + if not query_logs_by_group: + return [] + return query_logs_by_group + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 24535d4b..9ccecbed 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -18,7 +18,12 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ...pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs +from ...pagination import ( + SyncOffsetPageQueryLogs, + AsyncOffsetPageQueryLogs, + SyncOffsetPageQueryLogGroups, + AsyncOffsetPageQueryLogGroups, +) from ..._base_client import AsyncPaginator, make_request_options from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params from ...types.projects.query_log_list_response import QueryLogListResponse @@ -290,7 +295,7 @@ def list_groups( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> QueryLogListGroupsResponse: + ) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse]: """ List query log groups by project ID. @@ -323,8 +328,9 @@ def list_groups( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._get( + return self._get_api_list( f"/api/projects/{project_id}/query_logs/groups", + page=SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -349,7 +355,7 @@ def list_groups( query_log_list_groups_params.QueryLogListGroupsParams, ), ), - cast_to=QueryLogListGroupsResponse, + model=QueryLogListGroupsResponse, ) def start_remediation( @@ -622,7 +628,7 @@ async def list_by_group( cast_to=QueryLogListByGroupResponse, ) - async def list_groups( + def list_groups( self, project_id: str, *, @@ -649,7 +655,7 @@ async def list_groups( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> QueryLogListGroupsResponse: + ) -> AsyncPaginator[QueryLogListGroupsResponse, AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse]]: """ List query log groups by project ID. @@ -682,14 +688,15 @@ async def list_groups( """ if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return await self._get( + return self._get_api_list( f"/api/projects/{project_id}/query_logs/groups", + page=AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( + query=maybe_transform( { "created_at_end": created_at_end, "created_at_start": created_at_start, @@ -708,7 +715,7 @@ async def list_groups( query_log_list_groups_params.QueryLogListGroupsParams, ), ), - cast_to=QueryLogListGroupsResponse, + model=QueryLogListGroupsResponse, ) async def start_remediation( diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index e77dd87e..5d9222b1 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -8,41 +8,40 @@ __all__ = [ "QueryLogListGroupsResponse", - "QueryLogGroup", - "QueryLogGroupFormattedEscalationEvalScores", - "QueryLogGroupFormattedEvalScores", - "QueryLogGroupFormattedGuardrailEvalScores", - "QueryLogGroupFormattedNonGuardrailEvalScores", - "QueryLogGroupContext", - "QueryLogGroupDeterministicGuardrailsResults", + "FormattedEscalationEvalScores", + "FormattedEvalScores", + "FormattedGuardrailEvalScores", + "FormattedNonGuardrailEvalScores", + "Context", + "DeterministicGuardrailsResults", ] -class QueryLogGroupFormattedEscalationEvalScores(BaseModel): +class FormattedEscalationEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogGroupFormattedEvalScores(BaseModel): +class FormattedEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogGroupFormattedGuardrailEvalScores(BaseModel): +class FormattedGuardrailEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogGroupFormattedNonGuardrailEvalScores(BaseModel): +class FormattedNonGuardrailEvalScores(BaseModel): score: float status: Literal["pass", "fail"] -class QueryLogGroupContext(BaseModel): +class Context(BaseModel): content: str """The actual content/text of the document.""" @@ -59,7 +58,7 @@ class QueryLogGroupContext(BaseModel): """Title or heading of the document. Useful for display and context.""" -class QueryLogGroupDeterministicGuardrailsResults(BaseModel): +class DeterministicGuardrailsResults(BaseModel): guardrail_name: str should_guardrail: bool @@ -67,14 +66,14 @@ class QueryLogGroupDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class QueryLogGroup(BaseModel): +class QueryLogListGroupsResponse(BaseModel): id: str created_at: datetime - formatted_escalation_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEscalationEvalScores]] = None + formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None - formatted_eval_scores: Optional[Dict[str, QueryLogGroupFormattedEvalScores]] = None + formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None """Format evaluation scores for frontend display with pass/fail status. Returns: Dictionary mapping eval keys to their formatted representation: { @@ -82,9 +81,9 @@ class QueryLogGroup(BaseModel): eval_scores is None. """ - formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedGuardrailEvalScores]] = None + formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None - formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogGroupFormattedNonGuardrailEvalScores]] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None is_bad_response: bool @@ -103,7 +102,7 @@ class QueryLogGroup(BaseModel): was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" - context: Optional[List[QueryLogGroupContext]] = None + context: Optional[List[Context]] = None """RAG context used for the query""" custom_metadata: Optional[object] = None @@ -112,7 +111,7 @@ class QueryLogGroup(BaseModel): custom_metadata_keys: Optional[List[str]] = None """Keys of the custom metadata""" - deterministic_guardrails_results: Optional[Dict[str, QueryLogGroupDeterministicGuardrailsResults]] = None + deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None """Results of deterministic guardrails applied to the query""" escalated: Optional[bool] = None @@ -144,11 +143,3 @@ class QueryLogGroup(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - - -class QueryLogListGroupsResponse(BaseModel): - custom_metadata_columns: List[str] - - query_log_groups: List[QueryLogGroup] - - total_count: int diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index be867e22..cd4cd7d2 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -10,7 +10,12 @@ from codex import Codex, AsyncCodex from tests.utils import assert_matches_type from codex._utils import parse_datetime -from codex.pagination import SyncOffsetPageQueryLogs, AsyncOffsetPageQueryLogs +from codex.pagination import ( + SyncOffsetPageQueryLogs, + AsyncOffsetPageQueryLogs, + SyncOffsetPageQueryLogGroups, + AsyncOffsetPageQueryLogGroups, +) from codex.types.projects import ( QueryLogListResponse, QueryLogRetrieveResponse, @@ -209,7 +214,7 @@ def test_method_list_groups(self, client: Codex) -> None: query_log = client.projects.query_logs.list_groups( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -230,7 +235,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: sort="created_at", was_cache_hit=True, ) - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -242,7 +247,7 @@ def test_raw_response_list_groups(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = response.parse() - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -254,7 +259,7 @@ def test_streaming_response_list_groups(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = response.parse() - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) assert cast(Any, response.is_closed) is True @@ -508,7 +513,7 @@ async def test_method_list_groups(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list_groups( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -529,7 +534,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex sort="created_at", was_cache_hit=True, ) - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -541,7 +546,7 @@ async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = await response.parse() - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip() @parametrize @@ -553,7 +558,7 @@ async def test_streaming_response_list_groups(self, async_client: AsyncCodex) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" query_log = await response.parse() - assert_matches_type(QueryLogListGroupsResponse, query_log, path=["response"]) + assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) assert cast(Any, response.is_closed) is True From 6c5beb23983efa332a143d7f1d5f644b9808a1b4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:47:39 +0000 Subject: [PATCH 194/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 42 ++-- src/codex/types/project_validate_params.py | 279 ++++++++++++++++++++- tests/api_resources/test_projects.py | 34 ++- 4 files changed, 315 insertions(+), 42 deletions(-) diff --git a/.stats.yml b/.stats.yml index 01dc7b27..889336e3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: f17890d85522687a4c68702da9ad2efb +openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09 config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 1314b7be..dc01b112 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -488,17 +488,18 @@ def validate( project_id: str, *, context: str, - prompt: str, query: str, - response: str, + response: project_validate_params.Response, use_llm_matching: bool | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, + messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, + prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -526,9 +527,8 @@ def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. - messages: Optional message history to provide conversation context for the query. Used to - rewrite query into a self-contained version of itself. If not provided, the - query will be treated as self-contained. + messages: Message history to provide conversation context for the query. Messages contain + up to and including the latest user prompt to the LLM. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -615,8 +615,14 @@ def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be + generated from the messages. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + rewritten_question: The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -643,7 +649,6 @@ def validate( body=maybe_transform( { "context": context, - "prompt": prompt, "query": query, "response": response, "constrain_outputs": constrain_outputs, @@ -652,7 +657,9 @@ def validate( "eval_scores": eval_scores, "messages": messages, "options": options, + "prompt": prompt, "quality_preset": quality_preset, + "rewritten_question": rewritten_question, "task": task, }, project_validate_params.ProjectValidateParams, @@ -1090,17 +1097,18 @@ async def validate( project_id: str, *, context: str, - prompt: str, query: str, - response: str, + response: project_validate_params.Response, use_llm_matching: bool | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Optional[Iterable[project_validate_params.Message]] | NotGiven = NOT_GIVEN, + messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, + prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, + rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, @@ -1128,9 +1136,8 @@ async def validate( eval_scores: Scores assessing different aspects of the RAG system. If not provided, TLM will be used to generate scores. - messages: Optional message history to provide conversation context for the query. Used to - rewrite query into a self-contained version of itself. If not provided, the - query will be treated as self-contained. + messages: Message history to provide conversation context for the query. Messages contain + up to and including the latest user prompt to the LLM. options: Typed dict of advanced configuration options for the Trustworthy Language Model. Many of these configurations are determined by the quality preset selected @@ -1217,8 +1224,14 @@ async def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be + generated from the messages. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + rewritten_question: The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1245,7 +1258,6 @@ async def validate( body=await async_maybe_transform( { "context": context, - "prompt": prompt, "query": query, "response": response, "constrain_outputs": constrain_outputs, @@ -1254,7 +1266,9 @@ async def validate( "eval_scores": eval_scores, "messages": messages, "options": options, + "prompt": prompt, "quality_preset": quality_preset, + "rewritten_question": rewritten_question, "task": task, }, project_validate_params.ProjectValidateParams, diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 8b38ebfa..0862cbc1 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -2,6 +2,7 @@ from __future__ import annotations +import builtins from typing import Dict, List, Union, Iterable, Optional from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict @@ -9,6 +10,24 @@ __all__ = [ "ProjectValidateParams", + "Response", + "ResponseChatCompletion", + "ResponseChatCompletionChoice", + "ResponseChatCompletionChoiceMessage", + "ResponseChatCompletionChoiceMessageAnnotation", + "ResponseChatCompletionChoiceMessageAnnotationURLCitation", + "ResponseChatCompletionChoiceMessageAudio", + "ResponseChatCompletionChoiceMessageFunctionCall", + "ResponseChatCompletionChoiceMessageToolCall", + "ResponseChatCompletionChoiceMessageToolCallFunction", + "ResponseChatCompletionChoiceLogprobs", + "ResponseChatCompletionChoiceLogprobsContent", + "ResponseChatCompletionChoiceLogprobsContentTopLogprob", + "ResponseChatCompletionChoiceLogprobsRefusal", + "ResponseChatCompletionChoiceLogprobsRefusalTopLogprob", + "ResponseChatCompletionUsage", + "ResponseChatCompletionUsageCompletionTokensDetails", + "ResponseChatCompletionUsagePromptTokensDetails", "Message", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", @@ -41,11 +60,9 @@ class ProjectValidateParams(TypedDict, total=False): context: Required[str] - prompt: Required[str] - query: Required[str] - response: Required[str] + response: Required[Response] use_llm_matching: bool @@ -66,11 +83,10 @@ class ProjectValidateParams(TypedDict, total=False): If not provided, TLM will be used to generate scores. """ - messages: Optional[Iterable[Message]] - """Optional message history to provide conversation context for the query. + messages: Iterable[Message] + """Message history to provide conversation context for the query. - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. + Messages contain up to and including the latest user prompt to the LLM. """ options: Optional[Options] @@ -161,9 +177,21 @@ class ProjectValidateParams(TypedDict, total=False): - criteria: Instructions specifying the evaluation criteria. """ + prompt: Optional[str] + """The prompt to use for the TLM call. + + If not provided, the prompt will be generated from the messages. + """ + quality_preset: Literal["best", "high", "medium", "low", "base"] """The quality preset to use for the TLM or Trustworthy RAG API.""" + rewritten_question: Optional[str] + """ + The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + """ + task: Optional[str] x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] @@ -175,6 +203,243 @@ class ProjectValidateParams(TypedDict, total=False): x_stainless_package_version: Annotated[str, PropertyInfo(alias="x-stainless-package-version")] +class ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped(TypedDict, total=False): + end_index: Required[int] + + start_index: Required[int] + + title: Required[str] + + url: Required[str] + + +ResponseChatCompletionChoiceMessageAnnotationURLCitation: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageAnnotationTyped(TypedDict, total=False): + type: Required[Literal["url_citation"]] + + url_citation: Required[ResponseChatCompletionChoiceMessageAnnotationURLCitation] + + +ResponseChatCompletionChoiceMessageAnnotation: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAnnotationTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageAudioTyped(TypedDict, total=False): + id: Required[str] + + data: Required[str] + + expires_at: Required[int] + + transcript: Required[str] + + +ResponseChatCompletionChoiceMessageAudio: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAudioTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageFunctionCallTyped(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +ResponseChatCompletionChoiceMessageFunctionCall: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageFunctionCallTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageToolCallFunctionTyped(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +ResponseChatCompletionChoiceMessageToolCallFunction: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageToolCallFunctionTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageToolCallTyped(TypedDict, total=False): + id: Required[str] + + function: Required[ResponseChatCompletionChoiceMessageToolCallFunction] + + type: Required[Literal["function"]] + + +ResponseChatCompletionChoiceMessageToolCall: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageToolCallTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageTyped(TypedDict, total=False): + role: Required[Literal["assistant"]] + + annotations: Optional[Iterable[ResponseChatCompletionChoiceMessageAnnotation]] + + audio: Optional[ResponseChatCompletionChoiceMessageAudio] + + content: Optional[str] + + function_call: Optional[ResponseChatCompletionChoiceMessageFunctionCall] + + refusal: Optional[str] + + tool_calls: Optional[Iterable[ResponseChatCompletionChoiceMessageToolCall]] + + +ResponseChatCompletionChoiceMessage: TypeAlias = Union[ResponseChatCompletionChoiceMessageTyped, Dict[str, object]] + + +class ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsContentTopLogprob: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsContentTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsContentTopLogprob]] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsContent: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsContentTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsRefusalTopLogprob: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsRefusalTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsRefusalTopLogprob]] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsRefusal: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsRefusalTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsTyped(TypedDict, total=False): + content: Optional[Iterable[ResponseChatCompletionChoiceLogprobsContent]] + + refusal: Optional[Iterable[ResponseChatCompletionChoiceLogprobsRefusal]] + + +ResponseChatCompletionChoiceLogprobs: TypeAlias = Union[ResponseChatCompletionChoiceLogprobsTyped, Dict[str, object]] + + +class ResponseChatCompletionChoiceTyped(TypedDict, total=False): + finish_reason: Required[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] + + index: Required[int] + + message: Required[ResponseChatCompletionChoiceMessage] + + logprobs: Optional[ResponseChatCompletionChoiceLogprobs] + + +ResponseChatCompletionChoice: TypeAlias = Union[ResponseChatCompletionChoiceTyped, Dict[str, object]] + + +class ResponseChatCompletionUsageCompletionTokensDetailsTyped(TypedDict, total=False): + accepted_prediction_tokens: Optional[int] + + audio_tokens: Optional[int] + + reasoning_tokens: Optional[int] + + rejected_prediction_tokens: Optional[int] + + +ResponseChatCompletionUsageCompletionTokensDetails: TypeAlias = Union[ + ResponseChatCompletionUsageCompletionTokensDetailsTyped, Dict[str, object] +] + + +class ResponseChatCompletionUsagePromptTokensDetailsTyped(TypedDict, total=False): + audio_tokens: Optional[int] + + cached_tokens: Optional[int] + + +ResponseChatCompletionUsagePromptTokensDetails: TypeAlias = Union[ + ResponseChatCompletionUsagePromptTokensDetailsTyped, Dict[str, object] +] + + +class ResponseChatCompletionUsageTyped(TypedDict, total=False): + completion_tokens: Required[int] + + prompt_tokens: Required[int] + + total_tokens: Required[int] + + completion_tokens_details: Optional[ResponseChatCompletionUsageCompletionTokensDetails] + + prompt_tokens_details: Optional[ResponseChatCompletionUsagePromptTokensDetails] + + +ResponseChatCompletionUsage: TypeAlias = Union[ResponseChatCompletionUsageTyped, Dict[str, object]] + + +class ResponseChatCompletionTyped(TypedDict, total=False): + id: Required[str] + + choices: Required[Iterable[ResponseChatCompletionChoice]] + + created: Required[int] + + model: Required[str] + + object: Required[Literal["chat.completion"]] + + service_tier: Optional[Literal["scale", "default"]] + + system_fingerprint: Optional[str] + + usage: Optional[ResponseChatCompletionUsage] + + +ResponseChatCompletion: TypeAlias = Union[ResponseChatCompletionTyped, Dict[str, builtins.object]] + +Response: TypeAlias = Union[str, ResponseChatCompletion] + + class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): text: Required[str] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 0764d9a1..9ecffa09 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -632,9 +632,8 @@ def test_method_validate(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -644,9 +643,8 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", use_llm_matching=True, constrain_outputs=["string"], custom_eval_thresholds={"foo": 0}, @@ -670,7 +668,9 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: "similarity_measure": "similarity_measure", "use_self_reflection": True, }, + prompt="prompt", quality_preset="best", + rewritten_question="rewritten_question", task="task", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -685,9 +685,8 @@ def test_raw_response_validate(self, client: Codex) -> None: response = client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert response.is_closed is True @@ -701,9 +700,8 @@ def test_streaming_response_validate(self, client: Codex) -> None: with client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -720,9 +718,8 @@ def test_path_params_validate(self, client: Codex) -> None: client.projects.with_raw_response.validate( project_id="", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) @@ -1337,9 +1334,8 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None: project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -1349,9 +1345,8 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", use_llm_matching=True, constrain_outputs=["string"], custom_eval_thresholds={"foo": 0}, @@ -1375,7 +1370,9 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - "similarity_measure": "similarity_measure", "use_self_reflection": True, }, + prompt="prompt", quality_preset="best", + rewritten_question="rewritten_question", task="task", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", @@ -1390,9 +1387,8 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) assert response.is_closed is True @@ -1406,9 +1402,8 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No async with async_client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1425,7 +1420,6 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None: await async_client.projects.with_raw_response.validate( project_id="", context="context", - prompt="prompt", query="query", - response="response", + response="string", ) From 52449ccfbc4314ed3cb38ba9f27db91619a9dad3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:52:12 +0000 Subject: [PATCH 195/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aa848759..1c0bb885 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.22" + ".": "0.1.0-alpha.23" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a2a3d7c2..15dc82f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.22" +version = "0.1.0-alpha.23" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index a88a1c39..18f2dcb7 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.22" # x-release-please-version +__version__ = "0.1.0-alpha.23" # x-release-please-version From 55e790a28b0eb17015fdbc6f3bd900ecc0cef609 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:26:12 +0000 Subject: [PATCH 196/320] chore(internal): bump pinned h11 dep --- requirements-dev.lock | 4 ++-- requirements.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index a84b5f44..7999ff41 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,9 +48,9 @@ filelock==3.12.4 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via codex-sdk diff --git a/requirements.lock b/requirements.lock index a0807d8f..bde9133e 100644 --- a/requirements.lock +++ b/requirements.lock @@ -36,9 +36,9 @@ exceptiongroup==1.2.2 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via codex-sdk From 7a023f652f75390b63f4f473fb700ba8067c5ca3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:45:16 +0000 Subject: [PATCH 197/320] chore(package): mark python 3.13 as supported --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 15dc82f3..06612be9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", From 65839923960a3a26d4e51ec973845603aba02e53 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 02:40:45 +0000 Subject: [PATCH 198/320] fix(parsing): correctly handle nested discriminated unions --- src/codex/_models.py | 13 ++++++++----- tests/test_models.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index 4f214980..528d5680 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -2,9 +2,10 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, @@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: 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 is_basemodel(type_: type) -> bool: @@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. @@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object: type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() diff --git a/tests/test_models.py b/tests/test_models.py index c96609ce..3452a61b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -889,3 +889,48 @@ class ModelB(BaseModel): ) assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) From bc55fc805669aa42da078a62893177524a302d76 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 02:58:34 +0000 Subject: [PATCH 199/320] chore(readme): fix version rendering on pypi --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae3be8a6..7d673351 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Codex SDK API library -[![PyPI version]()](https://pypi.org/project/codex-sdk/) + +[![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/codex-sdk/) The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, From 4bf59bd427760308cac0eee75161ea2dc5d3b32c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:05:53 +0000 Subject: [PATCH 200/320] fix(client): don't send Content-Type header on GET requests --- pyproject.toml | 2 +- src/codex/_base_client.py | 11 +++++++++-- tests/test_client.py | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 06612be9..0e315f1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ Homepage = "https://github.com/cleanlab/codex-python" Repository = "https://github.com/cleanlab/codex-python" [project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"] [tool.rye] managed = true diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 1eca89e0..6da89f6c 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -529,6 +529,15 @@ def _build_request( # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + kwargs["json"] = json_data if is_given(json_data) else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, @@ -540,8 +549,6 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data if is_given(json_data) else None, - files=files, **kwargs, ) diff --git a/tests/test_client.py b/tests/test_client.py index c012f4d5..24749152 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -427,7 +427,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: Codex) -> 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"]}, @@ -1211,7 +1211,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncCodex) -> 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"]}, From 9e7a5a711d157d7443c41747a8f16df22e9aba59 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:17:47 +0000 Subject: [PATCH 201/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_validate_params.py | 102 ++++---- src/codex/types/project_validate_response.py | 4 +- .../query_log_list_by_group_response.py | 239 +++++++++++++++++- .../query_log_list_groups_response.py | 231 ++++++++++++++++- .../types/projects/query_log_list_response.py | 231 ++++++++++++++++- .../projects/query_log_retrieve_response.py | 231 ++++++++++++++++- ...remediation_list_resolved_logs_response.py | 237 ++++++++++++++++- .../projects/test_remediations.py | 20 +- tests/api_resources/test_projects.py | 20 +- 10 files changed, 1238 insertions(+), 79 deletions(-) diff --git a/.stats.yml b/.stats.yml index 889336e3..20ee8270 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 922886934580d0b2addcb6e26ada0e09 +openapi_spec_hash: b3a1a58600b52a20671bef2b25f5dbc4 config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 0862cbc1..7b85d061 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -33,23 +33,23 @@ "MessageChatCompletionDeveloperMessageParamContentUnionMember1", "MessageChatCompletionSystemMessageParam", "MessageChatCompletionSystemMessageParamContentUnionMember1", - "MessageChatCompletionUserMessageParam", - "MessageChatCompletionUserMessageParamContentUnionMember1", - "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam", - "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam", - "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam", - "MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "MessageChatCompletionUserMessageParamContentUnionMember1File", - "MessageChatCompletionUserMessageParamContentUnionMember1FileFile", - "MessageChatCompletionAssistantMessageParam", - "MessageChatCompletionAssistantMessageParamAudio", - "MessageChatCompletionAssistantMessageParamContentUnionMember1", - "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam", - "MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam", - "MessageChatCompletionAssistantMessageParamFunctionCall", - "MessageChatCompletionAssistantMessageParamToolCall", - "MessageChatCompletionAssistantMessageParamToolCallFunction", + "MessageChatCompletionUserMessageParamInput", + "MessageChatCompletionUserMessageParamInputContentUnionMember1", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamInputContentUnionMember1File", + "MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile", + "MessageChatCompletionAssistantMessageParamInput", + "MessageChatCompletionAssistantMessageParamInputAudio", + "MessageChatCompletionAssistantMessageParamInputContentUnionMember1", + "MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam", + "MessageChatCompletionAssistantMessageParamInputFunctionCall", + "MessageChatCompletionAssistantMessageParamInputToolCall", + "MessageChatCompletionAssistantMessageParamInputToolCallFunction", "MessageChatCompletionToolMessageParam", "MessageChatCompletionToolMessageParamContentUnionMember1", "MessageChatCompletionFunctionMessageParam", @@ -468,7 +468,7 @@ class MessageChatCompletionSystemMessageParam(TypedDict, total=False): name: str -class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam( +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam( TypedDict, total=False ): text: Required[str] @@ -476,7 +476,7 @@ class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionCont type: Required[Literal["text"]] -class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL( +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL( TypedDict, total=False ): url: Required[str] @@ -484,17 +484,17 @@ class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionCont detail: Literal["auto", "low", "high"] -class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam( +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam( TypedDict, total=False ): image_url: Required[ - MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParamImageURL + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL ] type: Required[Literal["image_url"]] -class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( TypedDict, total=False ): data: Required[str] @@ -502,17 +502,17 @@ class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionCont format: Required[Literal["wav", "mp3"]] -class MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam( +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam( TypedDict, total=False ): input_audio: Required[ - MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio ] type: Required[Literal["input_audio"]] -class MessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict, total=False): +class MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile(TypedDict, total=False): file_data: str file_id: str @@ -520,33 +520,33 @@ class MessageChatCompletionUserMessageParamContentUnionMember1FileFile(TypedDict filename: str -class MessageChatCompletionUserMessageParamContentUnionMember1File(TypedDict, total=False): - file: Required[MessageChatCompletionUserMessageParamContentUnionMember1FileFile] +class MessageChatCompletionUserMessageParamInputContentUnionMember1File(TypedDict, total=False): + file: Required[MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile] type: Required[Literal["file"]] -MessageChatCompletionUserMessageParamContentUnionMember1: TypeAlias = Union[ - MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartTextParam, - MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartImageParam, - MessageChatCompletionUserMessageParamContentUnionMember1ChatCompletionContentPartInputAudioParam, - MessageChatCompletionUserMessageParamContentUnionMember1File, +MessageChatCompletionUserMessageParamInputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam, + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam, + MessageChatCompletionUserMessageParamInputContentUnionMember1File, ] -class MessageChatCompletionUserMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[MessageChatCompletionUserMessageParamContentUnionMember1]]] +class MessageChatCompletionUserMessageParamInput(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionUserMessageParamInputContentUnionMember1]]] role: Required[Literal["user"]] name: str -class MessageChatCompletionAssistantMessageParamAudio(TypedDict, total=False): +class MessageChatCompletionAssistantMessageParamInputAudio(TypedDict, total=False): id: Required[str] -class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam( +class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam( TypedDict, total=False ): text: Required[str] @@ -554,7 +554,7 @@ class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletio type: Required[Literal["text"]] -class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam( +class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam( TypedDict, total=False ): refusal: Required[str] @@ -562,46 +562,46 @@ class MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletio type: Required[Literal["refusal"]] -MessageChatCompletionAssistantMessageParamContentUnionMember1: TypeAlias = Union[ - MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartTextParam, - MessageChatCompletionAssistantMessageParamContentUnionMember1ChatCompletionContentPartRefusalParam, +MessageChatCompletionAssistantMessageParamInputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam, ] -class MessageChatCompletionAssistantMessageParamFunctionCall(TypedDict, total=False): +class MessageChatCompletionAssistantMessageParamInputFunctionCall(TypedDict, total=False): arguments: Required[str] name: Required[str] -class MessageChatCompletionAssistantMessageParamToolCallFunction(TypedDict, total=False): +class MessageChatCompletionAssistantMessageParamInputToolCallFunction(TypedDict, total=False): arguments: Required[str] name: Required[str] -class MessageChatCompletionAssistantMessageParamToolCall(TypedDict, total=False): +class MessageChatCompletionAssistantMessageParamInputToolCall(TypedDict, total=False): id: Required[str] - function: Required[MessageChatCompletionAssistantMessageParamToolCallFunction] + function: Required[MessageChatCompletionAssistantMessageParamInputToolCallFunction] type: Required[Literal["function"]] -class MessageChatCompletionAssistantMessageParam(TypedDict, total=False): +class MessageChatCompletionAssistantMessageParamInput(TypedDict, total=False): role: Required[Literal["assistant"]] - audio: Optional[MessageChatCompletionAssistantMessageParamAudio] + audio: Optional[MessageChatCompletionAssistantMessageParamInputAudio] - content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamContentUnionMember1], None] + content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamInputContentUnionMember1], None] - function_call: Optional[MessageChatCompletionAssistantMessageParamFunctionCall] + function_call: Optional[MessageChatCompletionAssistantMessageParamInputFunctionCall] name: str refusal: Optional[str] - tool_calls: Iterable[MessageChatCompletionAssistantMessageParamToolCall] + tool_calls: Iterable[MessageChatCompletionAssistantMessageParamInputToolCall] class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False): @@ -629,8 +629,8 @@ class MessageChatCompletionFunctionMessageParam(TypedDict, total=False): Message: TypeAlias = Union[ MessageChatCompletionDeveloperMessageParam, MessageChatCompletionSystemMessageParam, - MessageChatCompletionUserMessageParam, - MessageChatCompletionAssistantMessageParam, + MessageChatCompletionUserMessageParamInput, + MessageChatCompletionAssistantMessageParamInput, MessageChatCompletionToolMessageParam, MessageChatCompletionFunctionMessageParam, ] diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index 3b06db2d..44883119 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -48,8 +48,8 @@ class ProjectValidateResponse(BaseModel): expert_answer: Optional[str] = None """ - Alternate SME-provided answer from Codex if the response was flagged as bad and - an answer was found in the Codex Project, or None otherwise. + Alternate SME-provided answer from Codex if a relevant answer was found in the + Codex Project, or None otherwise. """ is_bad_response: bool diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 16850735..ccd2d5ec 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -2,7 +2,7 @@ from typing import Dict, List, Union, Optional from datetime import datetime -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias from ..._models import BaseModel @@ -16,6 +16,31 @@ "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", "QueryLogsByGroupQueryLogDeterministicGuardrailsResults", + "QueryLogsByGroupQueryLogMessage", + "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", + "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall", + "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction", + "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1", + "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam", ] @@ -68,6 +93,200 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]] + + role: Literal["developer"] + + name: Optional[str] = None + + +class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]] + + role: Literal["system"] + + name: Optional[str] = None + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( + BaseModel +): + text: str + + type: Literal["text"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL( + BaseModel +): + url: str + + detail: Optional[Literal["auto", "low", "high"]] = None + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam( + BaseModel +): + image_url: QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL + + type: Literal["image_url"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + BaseModel +): + data: str + + format: Literal["wav", "mp3"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam( + BaseModel +): + input_audio: QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + + type: Literal["input_audio"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel): + file_data: Optional[str] = None + + file_id: Optional[str] = None + + filename: Optional[str] = None + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel): + file: QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile + + type: Literal["file"] + + +QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[ + QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam, + QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam, + QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File, +] + + +class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput(BaseModel): + content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1]] + + role: Literal["user"] + + name: Optional[str] = None + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( + BaseModel +): + text: str + + type: Literal["text"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str + + type: Literal["refusal"] + + +QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[ + str, List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None + ] = None + + function_call: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None + + name: Optional[str] = None + + refusal: Optional[str] = None + + tool_calls: Optional[List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1]] + + role: Literal["tool"] + + tool_call_id: str + + +class QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None + + name: str + + role: Literal["function"] + + +QueryLogsByGroupQueryLogMessage: TypeAlias = Union[ + QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam, + QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam, + QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput, + QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput, + QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam, + QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam, +] + + class QueryLogsByGroupQueryLog(BaseModel): id: str @@ -85,10 +304,14 @@ class QueryLogsByGroupQueryLog(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogsByGroupQueryLogFormattedGuardrailEvalScores]] = None + formatted_messages: Optional[str] = None + formatted_non_guardrail_eval_scores: Optional[ Dict[str, QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores] ] = None + formatted_original_question: Optional[str] = None + is_bad_response: bool needs_review: bool @@ -140,6 +363,20 @@ class QueryLogsByGroupQueryLog(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + messages: Optional[List[QueryLogsByGroupQueryLogMessage]] = None + """Optional message history to provide conversation context for the query. + + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. + """ + + original_question: Optional[str] = None + """The original question that was asked before any rewriting or processing. + + For all non-conversational RAG, original_question should be the same as the + final question seen in Codex. + """ + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 5d9222b1..9adb4227 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -2,7 +2,7 @@ from typing import Dict, List, Union, Optional from datetime import datetime -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias from ..._models import BaseModel @@ -14,6 +14,31 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "Message", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamOutput", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "MessageChatCompletionAssistantMessageParamOutput", + "MessageChatCompletionAssistantMessageParamOutputAudio", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam", + "MessageChatCompletionAssistantMessageParamOutputFunctionCall", + "MessageChatCompletionAssistantMessageParamOutputToolCall", + "MessageChatCompletionAssistantMessageParamOutputToolCallFunction", + "MessageChatCompletionToolMessageParam", + "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionFunctionMessageParam", ] @@ -66,6 +91,192 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] + + role: Literal["developer"] + + name: Optional[str] = None + + +class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] + + role: Literal["system"] + + name: Optional[str] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL( + BaseModel +): + url: str + + detail: Optional[Literal["auto", "low", "high"]] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(BaseModel): + image_url: MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL + + type: Literal["image_url"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + BaseModel +): + data: str + + format: Literal["wav", "mp3"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(BaseModel): + input_audio: ( + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + ) + + type: Literal["input_audio"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel): + file_data: Optional[str] = None + + file_id: Optional[str] = None + + filename: Optional[str] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel): + file: MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile + + type: Literal["file"] + + +MessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1File, +] + + +class MessageChatCompletionUserMessageParamOutput(BaseModel): + content: Union[str, List[MessageChatCompletionUserMessageParamOutputContentUnionMember1]] + + role: Literal["user"] + + name: Optional[str] = None + + +class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str + + type: Literal["refusal"] + + +MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class MessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None + + name: Optional[str] = None + + refusal: Optional[str] = None + + tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] + + role: Literal["tool"] + + tool_call_id: str + + +class MessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None + + name: str + + role: Literal["function"] + + +Message: TypeAlias = Union[ + MessageChatCompletionDeveloperMessageParam, + MessageChatCompletionSystemMessageParam, + MessageChatCompletionUserMessageParamOutput, + MessageChatCompletionAssistantMessageParamOutput, + MessageChatCompletionToolMessageParam, + MessageChatCompletionFunctionMessageParam, +] + + class QueryLogListGroupsResponse(BaseModel): id: str @@ -83,8 +294,12 @@ class QueryLogListGroupsResponse(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None + formatted_messages: Optional[str] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None + formatted_original_question: Optional[str] = None + is_bad_response: bool needs_review: bool @@ -138,6 +353,20 @@ class QueryLogListGroupsResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + messages: Optional[List[Message]] = None + """Optional message history to provide conversation context for the query. + + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. + """ + + original_question: Optional[str] = None + """The original question that was asked before any rewriting or processing. + + For all non-conversational RAG, original_question should be the same as the + final question seen in Codex. + """ + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index ccdeb038..f6fbba15 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -2,7 +2,7 @@ from typing import Dict, List, Union, Optional from datetime import datetime -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias from ..._models import BaseModel @@ -14,6 +14,31 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "Message", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamOutput", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "MessageChatCompletionAssistantMessageParamOutput", + "MessageChatCompletionAssistantMessageParamOutputAudio", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam", + "MessageChatCompletionAssistantMessageParamOutputFunctionCall", + "MessageChatCompletionAssistantMessageParamOutputToolCall", + "MessageChatCompletionAssistantMessageParamOutputToolCallFunction", + "MessageChatCompletionToolMessageParam", + "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionFunctionMessageParam", ] @@ -66,6 +91,192 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] + + role: Literal["developer"] + + name: Optional[str] = None + + +class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] + + role: Literal["system"] + + name: Optional[str] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL( + BaseModel +): + url: str + + detail: Optional[Literal["auto", "low", "high"]] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(BaseModel): + image_url: MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL + + type: Literal["image_url"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + BaseModel +): + data: str + + format: Literal["wav", "mp3"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(BaseModel): + input_audio: ( + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + ) + + type: Literal["input_audio"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel): + file_data: Optional[str] = None + + file_id: Optional[str] = None + + filename: Optional[str] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel): + file: MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile + + type: Literal["file"] + + +MessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1File, +] + + +class MessageChatCompletionUserMessageParamOutput(BaseModel): + content: Union[str, List[MessageChatCompletionUserMessageParamOutputContentUnionMember1]] + + role: Literal["user"] + + name: Optional[str] = None + + +class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str + + type: Literal["refusal"] + + +MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class MessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None + + name: Optional[str] = None + + refusal: Optional[str] = None + + tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] + + role: Literal["tool"] + + tool_call_id: str + + +class MessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None + + name: str + + role: Literal["function"] + + +Message: TypeAlias = Union[ + MessageChatCompletionDeveloperMessageParam, + MessageChatCompletionSystemMessageParam, + MessageChatCompletionUserMessageParamOutput, + MessageChatCompletionAssistantMessageParamOutput, + MessageChatCompletionToolMessageParam, + MessageChatCompletionFunctionMessageParam, +] + + class QueryLogListResponse(BaseModel): id: str @@ -83,8 +294,12 @@ class QueryLogListResponse(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None + formatted_messages: Optional[str] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None + formatted_original_question: Optional[str] = None + is_bad_response: bool project_id: str @@ -132,6 +347,20 @@ class QueryLogListResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + messages: Optional[List[Message]] = None + """Optional message history to provide conversation context for the query. + + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. + """ + + original_question: Optional[str] = None + """The original question that was asked before any rewriting or processing. + + For all non-conversational RAG, original_question should be the same as the + final question seen in Codex. + """ + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 380bacba..784009cf 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -2,7 +2,7 @@ from typing import Dict, List, Union, Optional from datetime import datetime -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias from ..._models import BaseModel @@ -14,6 +14,31 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "Message", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamOutput", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "MessageChatCompletionAssistantMessageParamOutput", + "MessageChatCompletionAssistantMessageParamOutputAudio", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam", + "MessageChatCompletionAssistantMessageParamOutputFunctionCall", + "MessageChatCompletionAssistantMessageParamOutputToolCall", + "MessageChatCompletionAssistantMessageParamOutputToolCallFunction", + "MessageChatCompletionToolMessageParam", + "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionFunctionMessageParam", ] @@ -66,6 +91,192 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] + + role: Literal["developer"] + + name: Optional[str] = None + + +class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] + + role: Literal["system"] + + name: Optional[str] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL( + BaseModel +): + url: str + + detail: Optional[Literal["auto", "low", "high"]] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam(BaseModel): + image_url: MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL + + type: Literal["image_url"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + BaseModel +): + data: str + + format: Literal["wav", "mp3"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam(BaseModel): + input_audio: ( + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + ) + + type: Literal["input_audio"] + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel): + file_data: Optional[str] = None + + file_id: Optional[str] = None + + filename: Optional[str] = None + + +class MessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel): + file: MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile + + type: Literal["file"] + + +MessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam, + MessageChatCompletionUserMessageParamOutputContentUnionMember1File, +] + + +class MessageChatCompletionUserMessageParamOutput(BaseModel): + content: Union[str, List[MessageChatCompletionUserMessageParamOutputContentUnionMember1]] + + role: Literal["user"] + + name: Optional[str] = None + + +class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str + + type: Literal["refusal"] + + +MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class MessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None + + name: Optional[str] = None + + refusal: Optional[str] = None + + tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class MessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] + + role: Literal["tool"] + + tool_call_id: str + + +class MessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None + + name: str + + role: Literal["function"] + + +Message: TypeAlias = Union[ + MessageChatCompletionDeveloperMessageParam, + MessageChatCompletionSystemMessageParam, + MessageChatCompletionUserMessageParamOutput, + MessageChatCompletionAssistantMessageParamOutput, + MessageChatCompletionToolMessageParam, + MessageChatCompletionFunctionMessageParam, +] + + class QueryLogRetrieveResponse(BaseModel): id: str @@ -83,8 +294,12 @@ class QueryLogRetrieveResponse(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, FormattedGuardrailEvalScores]] = None + formatted_messages: Optional[str] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, FormattedNonGuardrailEvalScores]] = None + formatted_original_question: Optional[str] = None + is_bad_response: bool needs_review: bool @@ -136,6 +351,20 @@ class QueryLogRetrieveResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + messages: Optional[List[Message]] = None + """Optional message history to provide conversation context for the query. + + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. + """ + + original_question: Optional[str] = None + """The original question that was asked before any rewriting or processing. + + For all non-conversational RAG, original_question should be the same as the + final question seen in Codex. + """ + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 876e7cec..1e0154cd 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -2,7 +2,7 @@ from typing import Dict, List, Union, Optional from datetime import datetime -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias from ..._models import BaseModel @@ -15,6 +15,31 @@ "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", "QueryLogDeterministicGuardrailsResults", + "QueryLogMessage", + "QueryLogMessageChatCompletionDeveloperMessageParam", + "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", + "QueryLogMessageChatCompletionSystemMessageParam", + "QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1", + "QueryLogMessageChatCompletionUserMessageParamOutput", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "QueryLogMessageChatCompletionAssistantMessageParamOutput", + "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio", + "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1", + "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam", + "QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall", + "QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall", + "QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction", + "QueryLogMessageChatCompletionToolMessageParam", + "QueryLogMessageChatCompletionToolMessageParamContentUnionMember1", + "QueryLogMessageChatCompletionFunctionMessageParam", ] @@ -67,6 +92,198 @@ class QueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]] + + role: Literal["developer"] + + name: Optional[str] = None + + +class QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class QueryLogMessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]] + + role: Literal["system"] + + name: Optional[str] = None + + +class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( + BaseModel +): + text: str + + type: Literal["text"] + + +class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL( + BaseModel +): + url: str + + detail: Optional[Literal["auto", "low", "high"]] = None + + +class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam( + BaseModel +): + image_url: QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL + + type: Literal["image_url"] + + +class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + BaseModel +): + data: str + + format: Literal["wav", "mp3"] + + +class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam( + BaseModel +): + input_audio: QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + + type: Literal["input_audio"] + + +class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile(BaseModel): + file_data: Optional[str] = None + + file_id: Optional[str] = None + + filename: Optional[str] = None + + +class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File(BaseModel): + file: QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile + + type: Literal["file"] + + +QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1: TypeAlias = Union[ + QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam, + QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam, + QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File, +] + + +class QueryLogMessageChatCompletionUserMessageParamOutput(BaseModel): + content: Union[str, List[QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1]] + + role: Literal["user"] + + name: Optional[str] = None + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( + BaseModel +): + text: str + + type: Literal["text"] + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str + + type: Literal["refusal"] + + +QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class QueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None + + name: Optional[str] = None + + refusal: Optional[str] = None + + tool_calls: Optional[List[QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class QueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): + text: str + + type: Literal["text"] + + +class QueryLogMessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[QueryLogMessageChatCompletionToolMessageParamContentUnionMember1]] + + role: Literal["tool"] + + tool_call_id: str + + +class QueryLogMessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None + + name: str + + role: Literal["function"] + + +QueryLogMessage: TypeAlias = Union[ + QueryLogMessageChatCompletionDeveloperMessageParam, + QueryLogMessageChatCompletionSystemMessageParam, + QueryLogMessageChatCompletionUserMessageParamOutput, + QueryLogMessageChatCompletionAssistantMessageParamOutput, + QueryLogMessageChatCompletionToolMessageParam, + QueryLogMessageChatCompletionFunctionMessageParam, +] + + class QueryLog(BaseModel): id: str @@ -84,8 +301,12 @@ class QueryLog(BaseModel): formatted_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedGuardrailEvalScores]] = None + formatted_messages: Optional[str] = None + formatted_non_guardrail_eval_scores: Optional[Dict[str, QueryLogFormattedNonGuardrailEvalScores]] = None + formatted_original_question: Optional[str] = None + is_bad_response: bool project_id: str @@ -133,6 +354,20 @@ class QueryLog(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + messages: Optional[List[QueryLogMessage]] = None + """Optional message history to provide conversation context for the query. + + Used to rewrite query into a self-contained version of itself. If not provided, + the query will be treated as self-contained. + """ + + original_question: Optional[str] = None + """The original question that was asked before any rewriting or processing. + + For all non-conversational RAG, original_question should be the same as the + final question seen in Codex. + """ + primary_eval_issue: Optional[str] = None """Primary issue identified in evaluation""" diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py index 947850f7..5866dbe7 100644 --- a/tests/api_resources/projects/test_remediations.py +++ b/tests/api_resources/projects/test_remediations.py @@ -35,7 +35,7 @@ class TestRemediations: def test_method_create(self, client: Codex) -> None: remediation = client.projects.remediations.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", ) assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) @@ -44,7 +44,7 @@ def test_method_create(self, client: Codex) -> None: def test_method_create_with_all_params(self, client: Codex) -> None: remediation = client.projects.remediations.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", answer="answer", draft_answer="draft_answer", ) @@ -55,7 +55,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: def test_raw_response_create(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", ) assert response.is_closed is True @@ -68,7 +68,7 @@ def test_raw_response_create(self, client: Codex) -> None: def test_streaming_response_create(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -84,7 +84,7 @@ def test_path_params_create(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): client.projects.remediations.with_raw_response.create( project_id="", - question="question", + question="x", ) @pytest.mark.skip() @@ -636,7 +636,7 @@ class TestAsyncRemediations: async def test_method_create(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", ) assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) @@ -645,7 +645,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", answer="answer", draft_answer="draft_answer", ) @@ -656,7 +656,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", ) assert response.is_closed is True @@ -669,7 +669,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.create( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="question", + question="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -685,7 +685,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): await async_client.projects.remediations.with_raw_response.create( project_id="", - question="question", + question="x", ) @pytest.mark.skip() diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 9ecffa09..4507741b 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -632,7 +632,7 @@ def test_method_validate(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -643,7 +643,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: project = client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", use_llm_matching=True, constrain_outputs=["string"], @@ -685,7 +685,7 @@ def test_raw_response_validate(self, client: Codex) -> None: response = client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", ) @@ -700,7 +700,7 @@ def test_streaming_response_validate(self, client: Codex) -> None: with client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", ) as response: assert not response.is_closed @@ -718,7 +718,7 @@ def test_path_params_validate(self, client: Codex) -> None: client.projects.with_raw_response.validate( project_id="", context="context", - query="query", + query="x", response="string", ) @@ -1334,7 +1334,7 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None: project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) @@ -1345,7 +1345,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - project = await async_client.projects.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", use_llm_matching=True, constrain_outputs=["string"], @@ -1387,7 +1387,7 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", ) @@ -1402,7 +1402,7 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No async with async_client.projects.with_streaming_response.validate( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", context="context", - query="query", + query="x", response="string", ) as response: assert not response.is_closed @@ -1420,6 +1420,6 @@ async def test_path_params_validate(self, async_client: AsyncCodex) -> None: await async_client.projects.with_raw_response.validate( project_id="", context="context", - query="query", + query="x", response="string", ) From 2abd377954bca4d7ee69cd5ea20e7f121a1e56a6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 22:17:46 +0000 Subject: [PATCH 202/320] feat(api): api update --- .stats.yml | 2 +- api.md | 2 +- src/codex/resources/projects/evals.py | 38 ++++++++++++++++--- src/codex/types/projects/__init__.py | 1 + src/codex/types/projects/eval_list_params.py | 16 ++++++++ .../types/projects/eval_list_response.py | 11 ++++-- tests/api_resources/projects/test_evals.py | 38 +++++++++++++++---- 7 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 src/codex/types/projects/eval_list_params.py diff --git a/.stats.yml b/.stats.yml index 20ee8270..b16c0569 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: b3a1a58600b52a20671bef2b25f5dbc4 +openapi_spec_hash: 1e86d5a7384400f4c3ddfb824fb31d84 config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/api.md b/api.md index 0c027913..22281ae1 100644 --- a/api.md +++ b/api.md @@ -192,7 +192,7 @@ Methods: - client.projects.evals.create(project_id, \*\*params) -> ProjectReturnSchema - client.projects.evals.update(path_eval_key, \*, project_id, \*\*params) -> ProjectReturnSchema -- client.projects.evals.list(project_id) -> EvalListResponse +- client.projects.evals.list(project_id, \*\*params) -> EvalListResponse - client.projects.evals.delete(eval_key, \*, project_id) -> ProjectReturnSchema ## QueryLogs diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py index 1fc95890..9de41b79 100644 --- a/src/codex/resources/projects/evals.py +++ b/src/codex/resources/projects/evals.py @@ -18,7 +18,7 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.projects import eval_create_params, eval_update_params +from ...types.projects import eval_list_params, eval_create_params, eval_update_params from ...types.project_return_schema import ProjectReturnSchema from ...types.projects.eval_list_response import EvalListResponse @@ -324,6 +324,9 @@ def list( self, project_id: str, *, + guardrails_only: bool | NotGiven = NOT_GIVEN, + limit: Optional[int] | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, # 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, @@ -332,7 +335,7 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EvalListResponse: """ - Get the evaluations config for a project. + Get the evaluations config for a project with optional pagination. Args: extra_headers: Send extra headers @@ -348,7 +351,18 @@ def list( return self._get( f"/api/projects/{project_id}/evals", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "guardrails_only": guardrails_only, + "limit": limit, + "offset": offset, + }, + eval_list_params.EvalListParams, + ), ), cast_to=EvalListResponse, ) @@ -689,6 +703,9 @@ async def list( self, project_id: str, *, + guardrails_only: bool | NotGiven = NOT_GIVEN, + limit: Optional[int] | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, # 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, @@ -697,7 +714,7 @@ async def list( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EvalListResponse: """ - Get the evaluations config for a project. + Get the evaluations config for a project with optional pagination. Args: extra_headers: Send extra headers @@ -713,7 +730,18 @@ async def list( return await self._get( f"/api/projects/{project_id}/evals", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "guardrails_only": guardrails_only, + "limit": limit, + "offset": offset, + }, + eval_list_params.EvalListParams, + ), ), cast_to=EvalListResponse, ) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 4f754703..cb2989f3 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from .eval_list_params import EvalListParams as EvalListParams from .access_key_schema import AccessKeySchema as AccessKeySchema from .eval_create_params import EvalCreateParams as EvalCreateParams from .eval_list_response import EvalListResponse as EvalListResponse diff --git a/src/codex/types/projects/eval_list_params.py b/src/codex/types/projects/eval_list_params.py new file mode 100644 index 00000000..b0f2fb6e --- /dev/null +++ b/src/codex/types/projects/eval_list_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["EvalListParams"] + + +class EvalListParams(TypedDict, total=False): + guardrails_only: bool + + limit: Optional[int] + + offset: int diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py index 48859b8d..eb2cb9a3 100644 --- a/src/codex/types/projects/eval_list_response.py +++ b/src/codex/types/projects/eval_list_response.py @@ -1,14 +1,14 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal from ..._models import BaseModel -__all__ = ["EvalListResponse", "EvalListResponseItem"] +__all__ = ["EvalListResponse", "Eval"] -class EvalListResponseItem(BaseModel): +class Eval(BaseModel): criteria: str """ The evaluation criteria text that describes what aspect is being evaluated and @@ -69,4 +69,7 @@ class EvalListResponseItem(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" -EvalListResponse: TypeAlias = List[EvalListResponseItem] +class EvalListResponse(BaseModel): + evals: List[Eval] + + total_count: int diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py index 22b83803..f36de276 100644 --- a/tests/api_resources/projects/test_evals.py +++ b/tests/api_resources/projects/test_evals.py @@ -259,7 +259,18 @@ def test_path_params_update_overload_2(self, client: Codex) -> None: @parametrize def test_method_list(self, client: Codex) -> None: eval = client.projects.evals.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(EvalListResponse, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + eval = client.projects.evals.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + guardrails_only=True, + limit=1, + offset=0, ) assert_matches_type(EvalListResponse, eval, path=["response"]) @@ -267,7 +278,7 @@ def test_method_list(self, client: Codex) -> None: @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.evals.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -279,7 +290,7 @@ def test_raw_response_list(self, client: Codex) -> None: @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.evals.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -294,7 +305,7 @@ def test_streaming_response_list(self, client: Codex) -> None: def test_path_params_list(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): client.projects.evals.with_raw_response.list( - "", + project_id="", ) @pytest.mark.skip() @@ -596,7 +607,18 @@ async def test_path_params_update_overload_2(self, async_client: AsyncCodex) -> @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(EvalListResponse, eval, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + eval = await async_client.projects.evals.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + guardrails_only=True, + limit=1, + offset=0, ) assert_matches_type(EvalListResponse, eval, path=["response"]) @@ -604,7 +626,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.evals.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -616,7 +638,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.evals.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -631,7 +653,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async def test_path_params_list(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): await async_client.projects.evals.with_raw_response.list( - "", + project_id="", ) @pytest.mark.skip() From 2c523e886f4757eca7939e916efa794ef7a97533 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 17:17:47 +0000 Subject: [PATCH 203/320] feat(api): api update --- .stats.yml | 2 +- .../types/projects/query_log_list_by_group_response.py | 6 +++--- src/codex/types/projects/query_log_list_groups_response.py | 6 +++--- src/codex/types/projects/query_log_list_response.py | 6 +++--- src/codex/types/projects/query_log_retrieve_response.py | 6 +++--- .../projects/remediation_list_resolved_logs_response.py | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.stats.yml b/.stats.yml index b16c0569..138e8d5c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 1e86d5a7384400f4c3ddfb824fb31d84 +openapi_spec_hash: fd2542df68972f34edeb819c58600791 config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index ccd2d5ec..0ff5909b 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -364,10 +364,10 @@ class QueryLogsByGroupQueryLog(BaseModel): """If true, the response was guardrailed""" messages: Optional[List[QueryLogsByGroupQueryLogMessage]] = None - """Optional message history to provide conversation context for the query. + """Message history to provide conversation context for the query. - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. + Used for TrustworthyRAG and to rewrite query into a self-contained version of + itself. """ original_question: Optional[str] = None diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 9adb4227..495fc563 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -354,10 +354,10 @@ class QueryLogListGroupsResponse(BaseModel): """If true, the response was guardrailed""" messages: Optional[List[Message]] = None - """Optional message history to provide conversation context for the query. + """Message history to provide conversation context for the query. - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. + Used for TrustworthyRAG and to rewrite query into a self-contained version of + itself. """ original_question: Optional[str] = None diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index f6fbba15..72a8cab6 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -348,10 +348,10 @@ class QueryLogListResponse(BaseModel): """If true, the response was guardrailed""" messages: Optional[List[Message]] = None - """Optional message history to provide conversation context for the query. + """Message history to provide conversation context for the query. - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. + Used for TrustworthyRAG and to rewrite query into a self-contained version of + itself. """ original_question: Optional[str] = None diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 784009cf..43242699 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -352,10 +352,10 @@ class QueryLogRetrieveResponse(BaseModel): """If true, the response was guardrailed""" messages: Optional[List[Message]] = None - """Optional message history to provide conversation context for the query. + """Message history to provide conversation context for the query. - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. + Used for TrustworthyRAG and to rewrite query into a self-contained version of + itself. """ original_question: Optional[str] = None diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 1e0154cd..cebfaf49 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -355,10 +355,10 @@ class QueryLog(BaseModel): """If true, the response was guardrailed""" messages: Optional[List[QueryLogMessage]] = None - """Optional message history to provide conversation context for the query. + """Message history to provide conversation context for the query. - Used to rewrite query into a self-contained version of itself. If not provided, - the query will be treated as self-contained. + Used for TrustworthyRAG and to rewrite query into a self-contained version of + itself. """ original_question: Optional[str] = None From c07c8df625040741da965b5e37ae5a0949f83fc9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 02:05:22 +0000 Subject: [PATCH 204/320] fix(parsing): ignore empty metadata --- src/codex/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index 528d5680..ffcbf67b 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -439,7 +439,7 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any] type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if metadata is not None: + if metadata is not None and len(metadata) > 0: meta: tuple[Any, ...] = tuple(metadata) elif is_annotated_type(type_): meta = get_args(type_)[1:] From f17da74ada41dc39463bad1abd3e0d546e318b37 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 22:17:30 +0000 Subject: [PATCH 205/320] feat(api): api update --- .stats.yml | 4 +- api.md | 1 - src/codex/resources/projects/projects.py | 102 --------------- src/codex/types/__init__.py | 1 - .../types/project_increment_queries_params.py | 11 -- tests/api_resources/test_projects.py | 118 ------------------ 6 files changed, 2 insertions(+), 235 deletions(-) delete mode 100644 src/codex/types/project_increment_queries_params.py diff --git a/.stats.yml b/.stats.yml index 138e8d5c..c31fbb05 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 55 -openapi_spec_hash: fd2542df68972f34edeb819c58600791 +configured_endpoints: 54 +openapi_spec_hash: 168bdf5a611596d39812ce7259416529 config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/api.md b/api.md index 22281ae1..1646d0b9 100644 --- a/api.md +++ b/api.md @@ -153,7 +153,6 @@ Methods: - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None - client.projects.export(project_id) -> object -- client.projects.increment_queries(project_id, \*\*params) -> object - client.projects.invite_sme(project_id, \*\*params) -> ProjectInviteSmeResponse - client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse - client.projects.validate(project_id, \*\*params) -> ProjectValidateResponse diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index dc01b112..3a109edf 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -2,7 +2,6 @@ from __future__ import annotations -import typing_extensions from typing import Dict, List, Iterable, Optional from typing_extensions import Literal @@ -22,7 +21,6 @@ project_update_params, project_validate_params, project_invite_sme_params, - project_increment_queries_params, project_retrieve_analytics_params, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven @@ -349,45 +347,6 @@ def export( cast_to=object, ) - @typing_extensions.deprecated("deprecated") - def increment_queries( - self, - project_id: str, - *, - count: int | NotGiven = NOT_GIVEN, - # 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, - ) -> object: - """ - Increment the queries metric for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return self._post( - f"/api/projects/{project_id}/increment_queries", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams), - ), - cast_to=object, - ) - def invite_sme( self, project_id: str, @@ -956,47 +915,6 @@ async def export( cast_to=object, ) - @typing_extensions.deprecated("deprecated") - async def increment_queries( - self, - project_id: str, - *, - count: int | NotGiven = NOT_GIVEN, - # 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, - ) -> object: - """ - Increment the queries metric for a project. - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - return await self._post( - f"/api/projects/{project_id}/increment_queries", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - {"count": count}, project_increment_queries_params.ProjectIncrementQueriesParams - ), - ), - cast_to=object, - ) - async def invite_sme( self, project_id: str, @@ -1308,11 +1226,6 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_raw_response_wrapper( projects.export, ) - self.increment_queries = ( # pyright: ignore[reportDeprecated] - to_raw_response_wrapper( - projects.increment_queries # pyright: ignore[reportDeprecated], - ) - ) self.invite_sme = to_raw_response_wrapper( projects.invite_sme, ) @@ -1362,11 +1275,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_raw_response_wrapper( projects.export, ) - self.increment_queries = ( # pyright: ignore[reportDeprecated] - async_to_raw_response_wrapper( - projects.increment_queries # pyright: ignore[reportDeprecated], - ) - ) self.invite_sme = async_to_raw_response_wrapper( projects.invite_sme, ) @@ -1416,11 +1324,6 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_streamed_response_wrapper( projects.export, ) - self.increment_queries = ( # pyright: ignore[reportDeprecated] - to_streamed_response_wrapper( - projects.increment_queries # pyright: ignore[reportDeprecated], - ) - ) self.invite_sme = to_streamed_response_wrapper( projects.invite_sme, ) @@ -1470,11 +1373,6 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_streamed_response_wrapper( projects.export, ) - self.increment_queries = ( # pyright: ignore[reportDeprecated] - async_to_streamed_response_wrapper( - projects.increment_queries # pyright: ignore[reportDeprecated], - ) - ) self.invite_sme = async_to_streamed_response_wrapper( projects.invite_sme, ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 70713a3b..daa16358 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -19,7 +19,6 @@ from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .project_invite_sme_response import ProjectInviteSmeResponse as ProjectInviteSmeResponse from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams -from .project_increment_queries_params import ProjectIncrementQueriesParams as ProjectIncrementQueriesParams from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse diff --git a/src/codex/types/project_increment_queries_params.py b/src/codex/types/project_increment_queries_params.py deleted file mode 100644 index f6043a76..00000000 --- a/src/codex/types/project_increment_queries_params.py +++ /dev/null @@ -1,11 +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__ = ["ProjectIncrementQueriesParams"] - - -class ProjectIncrementQueriesParams(TypedDict, total=False): - count: int diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 4507741b..ce4b7f53 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -18,8 +18,6 @@ ) from tests.utils import assert_matches_type -# pyright: reportDeprecated=false - base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -462,64 +460,6 @@ def test_path_params_export(self, client: Codex) -> None: "", ) - @pytest.mark.skip() - @parametrize - def test_method_increment_queries(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - project = client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert_matches_type(object, project, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_increment_queries_with_all_params(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - project = client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - count=0, - ) - - assert_matches_type(object, project, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_increment_queries(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - response = client.projects.with_raw_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - project = response.parse() - assert_matches_type(object, project, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_increment_queries(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - with client.projects.with_streaming_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - project = response.parse() - assert_matches_type(object, project, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_increment_queries(self, client: Codex) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.with_raw_response.increment_queries( - project_id="", - ) - @pytest.mark.skip() @parametrize def test_method_invite_sme(self, client: Codex) -> None: @@ -1164,64 +1104,6 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() - @parametrize - async def test_method_increment_queries(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - project = await async_client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert_matches_type(object, project, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_increment_queries_with_all_params(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - project = await async_client.projects.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - count=0, - ) - - assert_matches_type(object, project, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - response = await async_client.projects.with_raw_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - project = await response.parse() - assert_matches_type(object, project, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - async with async_client.projects.with_streaming_response.increment_queries( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - project = await response.parse() - assert_matches_type(object, project, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.with_raw_response.increment_queries( - project_id="", - ) - @pytest.mark.skip() @parametrize async def test_method_invite_sme(self, async_client: AsyncCodex) -> None: From 4fc59ec3153ed5d4869c4d08fda845d5bd86b1c6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 02:06:52 +0000 Subject: [PATCH 206/320] fix(parsing): parse extra field types --- src/codex/_models.py | 25 +++++++++++++++++++++++-- tests/test_models.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index ffcbf67b..b8387ce9 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -208,14 +208,18 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] else: fields_values[name] = field_get_default(field) + extra_field_type = _get_extra_fields_type(__cls) + _extra = {} for key, value in values.items(): if key not in model_fields: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + if PYDANTIC_V2: - _extra[key] = value + _extra[key] = parsed else: _fields_set.add(key) - fields_values[key] = value + fields_values[key] = parsed object.__setattr__(m, "__dict__", fields_values) @@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if not PYDANTIC_V2: + # 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: """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" if is_union(type_): diff --git a/tests/test_models.py b/tests/test_models.py index 3452a61b..a9897029 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone from typing_extensions import Literal, Annotated, TypeAliasType @@ -934,3 +934,30 @@ class Type2(BaseModel): ) assert isinstance(model, Type1) assert isinstance(model.value, InnerType2) + + +@pytest.mark.skipif(not PYDANTIC_V2, 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" From 95c6aab1a87026b300ebd880b57a33caf01a229b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:17:58 +0000 Subject: [PATCH 207/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 4 ++-- src/codex/types/projects/query_log_list_groups_params.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index c31fbb05..4cb0619e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 168bdf5a611596d39812ce7259416529 +openapi_spec_hash: 1ef62145e2247a442b75c87b23267e2d config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 9ccecbed..6fa490e8 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -286,7 +286,7 @@ def list_groups( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] + sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -646,7 +646,7 @@ def list_groups( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] + sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index cd82d9a7..f75ee299 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -44,7 +44,7 @@ class QueryLogListGroupsParams(TypedDict, total=False): ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" - sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank"]] + sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] was_cache_hit: Optional[bool] """Filter by cache hit status""" From 7b478f8059d12f8483dbb250715ad0f05b519c00 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:17:59 +0000 Subject: [PATCH 208/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_create_params.py | 2 ++ src/codex/types/project_list_response.py | 2 ++ src/codex/types/project_retrieve_response.py | 2 ++ src/codex/types/project_return_schema.py | 2 ++ src/codex/types/project_update_params.py | 2 ++ tests/api_resources/test_projects.py | 4 ++++ 7 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 4cb0619e..de1764db 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 1ef62145e2247a442b75c87b23267e2d +openapi_spec_hash: 1c6e7d5ed06d72868a57e64381bc473c config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index a8369782..c75023f0 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -333,4 +333,6 @@ class Config(TypedDict, total=False): query_use_llm_matching: bool + tlm_evals_model: str + upper_llm_match_distance_threshold: float diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index c39bf086..4ac38497 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -323,6 +323,8 @@ class ProjectConfig(BaseModel): query_use_llm_matching: Optional[bool] = None + tlm_evals_model: Optional[str] = None + upper_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 7d1f8edd..6e87d655 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -322,6 +322,8 @@ class Config(BaseModel): query_use_llm_matching: Optional[bool] = None + tlm_evals_model: Optional[str] = None + upper_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 170d7994..bb087cd0 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -322,6 +322,8 @@ class Config(BaseModel): query_use_llm_matching: Optional[bool] = None + tlm_evals_model: Optional[str] = None + upper_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 3e244411..c550b436 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -331,4 +331,6 @@ class Config(TypedDict, total=False): query_use_llm_matching: bool + tlm_evals_model: str + upper_llm_match_distance_threshold: float diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index ce4b7f53..9312ca0a 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -118,6 +118,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, name="name", @@ -293,6 +294,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, description="description", @@ -762,6 +764,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, name="name", @@ -937,6 +940,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, description="description", From df7fb101acaa6d9c3327cd1daaaf0ae571a514e8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 03:08:09 +0000 Subject: [PATCH 209/320] chore(project): add settings file for vscode --- .gitignore | 1 - .vscode/settings.json | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 87797408..95ceb189 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .prism.log -.vscode _dev __pycache__ diff --git a/.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", +} From ec8118f9ddabd922ad95e7006552d764992141ea Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 21:18:01 +0000 Subject: [PATCH 210/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_validate_params.py | 172 ++++++++--------- .../query_log_list_by_group_response.py | 176 +++++++++--------- .../query_log_list_groups_response.py | 168 ++++++++--------- .../types/projects/query_log_list_response.py | 168 ++++++++--------- .../projects/query_log_retrieve_response.py | 168 ++++++++--------- ...remediation_list_resolved_logs_response.py | 172 ++++++++--------- tests/api_resources/test_projects.py | 36 +++- 8 files changed, 547 insertions(+), 515 deletions(-) diff --git a/.stats.yml b/.stats.yml index de1764db..9fb51405 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 1c6e7d5ed06d72868a57e64381bc473c +openapi_spec_hash: f7b67b502828e6d0ca3944d40d00d89b config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 7b85d061..081dd2a5 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -29,19 +29,6 @@ "ResponseChatCompletionUsageCompletionTokensDetails", "ResponseChatCompletionUsagePromptTokensDetails", "Message", - "MessageChatCompletionDeveloperMessageParam", - "MessageChatCompletionDeveloperMessageParamContentUnionMember1", - "MessageChatCompletionSystemMessageParam", - "MessageChatCompletionSystemMessageParamContentUnionMember1", - "MessageChatCompletionUserMessageParamInput", - "MessageChatCompletionUserMessageParamInputContentUnionMember1", - "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam", - "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam", - "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam", - "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "MessageChatCompletionUserMessageParamInputContentUnionMember1File", - "MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile", "MessageChatCompletionAssistantMessageParamInput", "MessageChatCompletionAssistantMessageParamInputAudio", "MessageChatCompletionAssistantMessageParamInputContentUnionMember1", @@ -52,7 +39,20 @@ "MessageChatCompletionAssistantMessageParamInputToolCallFunction", "MessageChatCompletionToolMessageParam", "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamInput", + "MessageChatCompletionUserMessageParamInputContentUnionMember1", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamInputContentUnionMember1File", + "MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", "MessageChatCompletionFunctionMessageParam", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", "Options", ] @@ -440,32 +440,80 @@ class ResponseChatCompletionTyped(TypedDict, total=False): Response: TypeAlias = Union[str, ResponseChatCompletion] -class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): +class MessageChatCompletionAssistantMessageParamInputAudio(TypedDict, total=False): + id: Required[str] + + +class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam( + TypedDict, total=False +): text: Required[str] type: Required[Literal["text"]] -class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]] +class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam( + TypedDict, total=False +): + refusal: Required[str] + + type: Required[Literal["refusal"]] - role: Required[Literal["developer"]] + +MessageChatCompletionAssistantMessageParamInputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamInputFunctionCall(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class MessageChatCompletionAssistantMessageParamInputToolCallFunction(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class MessageChatCompletionAssistantMessageParamInputToolCall(TypedDict, total=False): + id: Required[str] + + function: Required[MessageChatCompletionAssistantMessageParamInputToolCallFunction] + + type: Required[Literal["function"]] + + +class MessageChatCompletionAssistantMessageParamInput(TypedDict, total=False): + role: Required[Literal["assistant"]] + + audio: Optional[MessageChatCompletionAssistantMessageParamInputAudio] + + content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamInputContentUnionMember1], None] + + function_call: Optional[MessageChatCompletionAssistantMessageParamInputFunctionCall] name: str + refusal: Optional[str] -class MessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False): + tool_calls: Iterable[MessageChatCompletionAssistantMessageParamInputToolCall] + + +class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False): text: Required[str] type: Required[Literal["text"]] -class MessageChatCompletionSystemMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[MessageChatCompletionSystemMessageParamContentUnionMember1]]] +class MessageChatCompletionToolMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionToolMessageParamContentUnionMember1]]] - role: Required[Literal["system"]] + role: Required[Literal["tool"]] - name: str + tool_call_id: Required[str] class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam( @@ -542,97 +590,49 @@ class MessageChatCompletionUserMessageParamInput(TypedDict, total=False): name: str -class MessageChatCompletionAssistantMessageParamInputAudio(TypedDict, total=False): - id: Required[str] - - -class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam( - TypedDict, total=False -): +class MessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False): text: Required[str] type: Required[Literal["text"]] -class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam( - TypedDict, total=False -): - refusal: Required[str] - - type: Required[Literal["refusal"]] - - -MessageChatCompletionAssistantMessageParamInputContentUnionMember1: TypeAlias = Union[ - MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam, - MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam, -] - +class MessageChatCompletionSystemMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionSystemMessageParamContentUnionMember1]]] -class MessageChatCompletionAssistantMessageParamInputFunctionCall(TypedDict, total=False): - arguments: Required[str] + role: Required[Literal["system"]] - name: Required[str] + name: str -class MessageChatCompletionAssistantMessageParamInputToolCallFunction(TypedDict, total=False): - arguments: Required[str] +class MessageChatCompletionFunctionMessageParam(TypedDict, total=False): + content: Required[Optional[str]] name: Required[str] - -class MessageChatCompletionAssistantMessageParamInputToolCall(TypedDict, total=False): - id: Required[str] - - function: Required[MessageChatCompletionAssistantMessageParamInputToolCallFunction] - - type: Required[Literal["function"]] - - -class MessageChatCompletionAssistantMessageParamInput(TypedDict, total=False): - role: Required[Literal["assistant"]] - - audio: Optional[MessageChatCompletionAssistantMessageParamInputAudio] - - content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamInputContentUnionMember1], None] - - function_call: Optional[MessageChatCompletionAssistantMessageParamInputFunctionCall] - - name: str - - refusal: Optional[str] - - tool_calls: Iterable[MessageChatCompletionAssistantMessageParamInputToolCall] + role: Required[Literal["function"]] -class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False): +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): text: Required[str] type: Required[Literal["text"]] -class MessageChatCompletionToolMessageParam(TypedDict, total=False): - content: Required[Union[str, Iterable[MessageChatCompletionToolMessageParamContentUnionMember1]]] - - role: Required[Literal["tool"]] - - tool_call_id: Required[str] - - -class MessageChatCompletionFunctionMessageParam(TypedDict, total=False): - content: Required[Optional[str]] +class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]] - name: Required[str] + role: Required[Literal["developer"]] - role: Required[Literal["function"]] + name: str Message: TypeAlias = Union[ - MessageChatCompletionDeveloperMessageParam, - MessageChatCompletionSystemMessageParam, - MessageChatCompletionUserMessageParamInput, MessageChatCompletionAssistantMessageParamInput, MessageChatCompletionToolMessageParam, + MessageChatCompletionUserMessageParamInput, + MessageChatCompletionSystemMessageParam, MessageChatCompletionFunctionMessageParam, + MessageChatCompletionDeveloperMessageParam, ] diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 0ff5909b..ae49b954 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -17,19 +17,6 @@ "QueryLogsByGroupQueryLogContext", "QueryLogsByGroupQueryLogDeterministicGuardrailsResults", "QueryLogsByGroupQueryLogMessage", - "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam", - "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", - "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam", - "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File", - "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1", @@ -40,7 +27,20 @@ "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction", "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam", "QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1", "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam", + "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", ] @@ -93,32 +93,82 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( + BaseModel +): text: str type: Literal["text"] -class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): - content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]] +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str - role: Literal["developer"] + type: Literal["refusal"] + + +QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[ + str, List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None + ] = None + + function_call: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None name: Optional[str] = None + refusal: Optional[str] = None -class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + tool_calls: Optional[List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam(BaseModel): - content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]] +class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1]] - role: Literal["system"] + role: Literal["tool"] - name: Optional[str] = None + tool_call_id: str class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( @@ -191,99 +241,49 @@ class QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput(BaseMo name: Optional[str] = None -class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): - id: str - - -class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( - BaseModel -): +class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( - BaseModel -): - refusal: str - - type: Literal["refusal"] - - -QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ - QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, - QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, -] - +class QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]] -class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): - arguments: str + role: Literal["system"] - name: str + name: Optional[str] = None -class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): - arguments: str +class QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None name: str - -class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): - id: str - - function: QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction - - type: Literal["function"] - - -class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel): - role: Literal["assistant"] - - audio: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None - - content: Union[ - str, List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None - ] = None - - function_call: Optional[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None - - name: Optional[str] = None - - refusal: Optional[str] = None - - tool_calls: Optional[List[QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None + role: Literal["function"] -class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): +class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam(BaseModel): - content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParamContentUnionMember1]] - - role: Literal["tool"] - - tool_call_id: str - - -class QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam(BaseModel): - content: Optional[str] = None +class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]] - name: str + role: Literal["developer"] - role: Literal["function"] + name: Optional[str] = None QueryLogsByGroupQueryLogMessage: TypeAlias = Union[ - QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam, - QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam, - QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput, QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput, QueryLogsByGroupQueryLogMessageChatCompletionToolMessageParam, + QueryLogsByGroupQueryLogMessageChatCompletionUserMessageParamOutput, + QueryLogsByGroupQueryLogMessageChatCompletionSystemMessageParam, QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam, + QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam, ] diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 495fc563..cc3b208b 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -15,19 +15,6 @@ "Context", "DeterministicGuardrailsResults", "Message", - "MessageChatCompletionDeveloperMessageParam", - "MessageChatCompletionDeveloperMessageParamContentUnionMember1", - "MessageChatCompletionSystemMessageParam", - "MessageChatCompletionSystemMessageParamContentUnionMember1", - "MessageChatCompletionUserMessageParamOutput", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1", @@ -38,7 +25,20 @@ "MessageChatCompletionAssistantMessageParamOutputToolCallFunction", "MessageChatCompletionToolMessageParam", "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamOutput", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", "MessageChatCompletionFunctionMessageParam", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", ] @@ -91,32 +91,78 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): +class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionDeveloperMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str - role: Literal["developer"] + type: Literal["refusal"] + + +MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class MessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None name: Optional[str] = None + refusal: Optional[str] = None -class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionSystemMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] +class MessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] - role: Literal["system"] + role: Literal["tool"] - name: Optional[str] = None + tool_call_id: str class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): @@ -185,95 +231,49 @@ class MessageChatCompletionUserMessageParamOutput(BaseModel): name: Optional[str] = None -class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): - id: str - - -class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): +class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( - BaseModel -): - refusal: str - - type: Literal["refusal"] - - -MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ - MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, - MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, -] - +class MessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] -class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): - arguments: str + role: Literal["system"] - name: str + name: Optional[str] = None -class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): - arguments: str +class MessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None name: str - -class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): - id: str - - function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction - - type: Literal["function"] - - -class MessageChatCompletionAssistantMessageParamOutput(BaseModel): - role: Literal["assistant"] - - audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None - - content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None - - function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None - - name: Optional[str] = None - - refusal: Optional[str] = None - - tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + role: Literal["function"] -class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionToolMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] - - role: Literal["tool"] - - tool_call_id: str - - -class MessageChatCompletionFunctionMessageParam(BaseModel): - content: Optional[str] = None +class MessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] - name: str + role: Literal["developer"] - role: Literal["function"] + name: Optional[str] = None Message: TypeAlias = Union[ - MessageChatCompletionDeveloperMessageParam, - MessageChatCompletionSystemMessageParam, - MessageChatCompletionUserMessageParamOutput, MessageChatCompletionAssistantMessageParamOutput, MessageChatCompletionToolMessageParam, + MessageChatCompletionUserMessageParamOutput, + MessageChatCompletionSystemMessageParam, MessageChatCompletionFunctionMessageParam, + MessageChatCompletionDeveloperMessageParam, ] diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 72a8cab6..0778898f 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -15,19 +15,6 @@ "Context", "DeterministicGuardrailsResults", "Message", - "MessageChatCompletionDeveloperMessageParam", - "MessageChatCompletionDeveloperMessageParamContentUnionMember1", - "MessageChatCompletionSystemMessageParam", - "MessageChatCompletionSystemMessageParamContentUnionMember1", - "MessageChatCompletionUserMessageParamOutput", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1", @@ -38,7 +25,20 @@ "MessageChatCompletionAssistantMessageParamOutputToolCallFunction", "MessageChatCompletionToolMessageParam", "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamOutput", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", "MessageChatCompletionFunctionMessageParam", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", ] @@ -91,32 +91,78 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): +class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionDeveloperMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str - role: Literal["developer"] + type: Literal["refusal"] + + +MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class MessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None name: Optional[str] = None + refusal: Optional[str] = None -class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionSystemMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] +class MessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] - role: Literal["system"] + role: Literal["tool"] - name: Optional[str] = None + tool_call_id: str class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): @@ -185,95 +231,49 @@ class MessageChatCompletionUserMessageParamOutput(BaseModel): name: Optional[str] = None -class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): - id: str - - -class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): +class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( - BaseModel -): - refusal: str - - type: Literal["refusal"] - - -MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ - MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, - MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, -] - +class MessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] -class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): - arguments: str + role: Literal["system"] - name: str + name: Optional[str] = None -class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): - arguments: str +class MessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None name: str - -class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): - id: str - - function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction - - type: Literal["function"] - - -class MessageChatCompletionAssistantMessageParamOutput(BaseModel): - role: Literal["assistant"] - - audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None - - content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None - - function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None - - name: Optional[str] = None - - refusal: Optional[str] = None - - tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + role: Literal["function"] -class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionToolMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] - - role: Literal["tool"] - - tool_call_id: str - - -class MessageChatCompletionFunctionMessageParam(BaseModel): - content: Optional[str] = None +class MessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] - name: str + role: Literal["developer"] - role: Literal["function"] + name: Optional[str] = None Message: TypeAlias = Union[ - MessageChatCompletionDeveloperMessageParam, - MessageChatCompletionSystemMessageParam, - MessageChatCompletionUserMessageParamOutput, MessageChatCompletionAssistantMessageParamOutput, MessageChatCompletionToolMessageParam, + MessageChatCompletionUserMessageParamOutput, + MessageChatCompletionSystemMessageParam, MessageChatCompletionFunctionMessageParam, + MessageChatCompletionDeveloperMessageParam, ] diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 43242699..2751ef21 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -15,19 +15,6 @@ "Context", "DeterministicGuardrailsResults", "Message", - "MessageChatCompletionDeveloperMessageParam", - "MessageChatCompletionDeveloperMessageParamContentUnionMember1", - "MessageChatCompletionSystemMessageParam", - "MessageChatCompletionSystemMessageParamContentUnionMember1", - "MessageChatCompletionUserMessageParamOutput", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", - "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", "MessageChatCompletionAssistantMessageParamOutputContentUnionMember1", @@ -38,7 +25,20 @@ "MessageChatCompletionAssistantMessageParamOutputToolCallFunction", "MessageChatCompletionToolMessageParam", "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamOutput", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "MessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", "MessageChatCompletionFunctionMessageParam", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", ] @@ -91,32 +91,78 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): +class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionDeveloperMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] +class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str - role: Literal["developer"] + type: Literal["refusal"] + + +MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class MessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None name: Optional[str] = None + refusal: Optional[str] = None -class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionSystemMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] +class MessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] - role: Literal["system"] + role: Literal["tool"] - name: Optional[str] = None + tool_call_id: str class MessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): @@ -185,95 +231,49 @@ class MessageChatCompletionUserMessageParamOutput(BaseModel): name: Optional[str] = None -class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): - id: str - - -class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam(BaseModel): +class MessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( - BaseModel -): - refusal: str - - type: Literal["refusal"] - - -MessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ - MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, - MessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, -] - +class MessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionSystemMessageParamContentUnionMember1]] -class MessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): - arguments: str + role: Literal["system"] - name: str + name: Optional[str] = None -class MessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): - arguments: str +class MessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None name: str - -class MessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): - id: str - - function: MessageChatCompletionAssistantMessageParamOutputToolCallFunction - - type: Literal["function"] - - -class MessageChatCompletionAssistantMessageParamOutput(BaseModel): - role: Literal["assistant"] - - audio: Optional[MessageChatCompletionAssistantMessageParamOutputAudio] = None - - content: Union[str, List[MessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None - - function_call: Optional[MessageChatCompletionAssistantMessageParamOutputFunctionCall] = None - - name: Optional[str] = None - - refusal: Optional[str] = None - - tool_calls: Optional[List[MessageChatCompletionAssistantMessageParamOutputToolCall]] = None + role: Literal["function"] -class MessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class MessageChatCompletionToolMessageParam(BaseModel): - content: Union[str, List[MessageChatCompletionToolMessageParamContentUnionMember1]] - - role: Literal["tool"] - - tool_call_id: str - - -class MessageChatCompletionFunctionMessageParam(BaseModel): - content: Optional[str] = None +class MessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[MessageChatCompletionDeveloperMessageParamContentUnionMember1]] - name: str + role: Literal["developer"] - role: Literal["function"] + name: Optional[str] = None Message: TypeAlias = Union[ - MessageChatCompletionDeveloperMessageParam, - MessageChatCompletionSystemMessageParam, - MessageChatCompletionUserMessageParamOutput, MessageChatCompletionAssistantMessageParamOutput, MessageChatCompletionToolMessageParam, + MessageChatCompletionUserMessageParamOutput, + MessageChatCompletionSystemMessageParam, MessageChatCompletionFunctionMessageParam, + MessageChatCompletionDeveloperMessageParam, ] diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index cebfaf49..d56f9a41 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -16,19 +16,6 @@ "QueryLogContext", "QueryLogDeterministicGuardrailsResults", "QueryLogMessage", - "QueryLogMessageChatCompletionDeveloperMessageParam", - "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", - "QueryLogMessageChatCompletionSystemMessageParam", - "QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1", - "QueryLogMessageChatCompletionUserMessageParamOutput", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File", - "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", "QueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio", "QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1", @@ -39,7 +26,20 @@ "QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction", "QueryLogMessageChatCompletionToolMessageParam", "QueryLogMessageChatCompletionToolMessageParamContentUnionMember1", + "QueryLogMessageChatCompletionUserMessageParamOutput", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParam", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1File", + "QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1FileFile", + "QueryLogMessageChatCompletionSystemMessageParam", + "QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1", "QueryLogMessageChatCompletionFunctionMessageParam", + "QueryLogMessageChatCompletionDeveloperMessageParam", + "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", ] @@ -92,32 +92,80 @@ class QueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): +class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): + id: str + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( + BaseModel +): text: str type: Literal["text"] -class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): - content: Union[str, List[QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]] +class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( + BaseModel +): + refusal: str + + type: Literal["refusal"] - role: Literal["developer"] + +QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ + QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, + QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): + arguments: str + + name: str + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): + id: str + + function: QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction + + type: Literal["function"] + + +class QueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel): + role: Literal["assistant"] + + audio: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None + + content: Union[str, List[QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None + + function_call: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None name: Optional[str] = None + refusal: Optional[str] = None -class QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): + tool_calls: Optional[List[QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None + + +class QueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class QueryLogMessageChatCompletionSystemMessageParam(BaseModel): - content: Union[str, List[QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]] +class QueryLogMessageChatCompletionToolMessageParam(BaseModel): + content: Union[str, List[QueryLogMessageChatCompletionToolMessageParamContentUnionMember1]] - role: Literal["system"] + role: Literal["tool"] - name: Optional[str] = None + tool_call_id: str class QueryLogMessageChatCompletionUserMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( @@ -190,97 +238,49 @@ class QueryLogMessageChatCompletionUserMessageParamOutput(BaseModel): name: Optional[str] = None -class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): - id: str - - -class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam( - BaseModel -): +class QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam( - BaseModel -): - refusal: str - - type: Literal["refusal"] - - -QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1: TypeAlias = Union[ - QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartTextParam, - QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1ChatCompletionContentPartRefusalParam, -] - +class QueryLogMessageChatCompletionSystemMessageParam(BaseModel): + content: Union[str, List[QueryLogMessageChatCompletionSystemMessageParamContentUnionMember1]] -class QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall(BaseModel): - arguments: str + role: Literal["system"] - name: str + name: Optional[str] = None -class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction(BaseModel): - arguments: str +class QueryLogMessageChatCompletionFunctionMessageParam(BaseModel): + content: Optional[str] = None name: str - -class QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall(BaseModel): - id: str - - function: QueryLogMessageChatCompletionAssistantMessageParamOutputToolCallFunction - - type: Literal["function"] - - -class QueryLogMessageChatCompletionAssistantMessageParamOutput(BaseModel): - role: Literal["assistant"] - - audio: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputAudio] = None - - content: Union[str, List[QueryLogMessageChatCompletionAssistantMessageParamOutputContentUnionMember1], None] = None - - function_call: Optional[QueryLogMessageChatCompletionAssistantMessageParamOutputFunctionCall] = None - - name: Optional[str] = None - - refusal: Optional[str] = None - - tool_calls: Optional[List[QueryLogMessageChatCompletionAssistantMessageParamOutputToolCall]] = None + role: Literal["function"] -class QueryLogMessageChatCompletionToolMessageParamContentUnionMember1(BaseModel): +class QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1(BaseModel): text: str type: Literal["text"] -class QueryLogMessageChatCompletionToolMessageParam(BaseModel): - content: Union[str, List[QueryLogMessageChatCompletionToolMessageParamContentUnionMember1]] - - role: Literal["tool"] - - tool_call_id: str - - -class QueryLogMessageChatCompletionFunctionMessageParam(BaseModel): - content: Optional[str] = None +class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): + content: Union[str, List[QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1]] - name: str + role: Literal["developer"] - role: Literal["function"] + name: Optional[str] = None QueryLogMessage: TypeAlias = Union[ - QueryLogMessageChatCompletionDeveloperMessageParam, - QueryLogMessageChatCompletionSystemMessageParam, - QueryLogMessageChatCompletionUserMessageParamOutput, QueryLogMessageChatCompletionAssistantMessageParamOutput, QueryLogMessageChatCompletionToolMessageParam, + QueryLogMessageChatCompletionUserMessageParamOutput, + QueryLogMessageChatCompletionSystemMessageParam, QueryLogMessageChatCompletionFunctionMessageParam, + QueryLogMessageChatCompletionDeveloperMessageParam, ] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 9312ca0a..ae3f4f05 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -594,9 +594,25 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: eval_scores={"foo": 0}, messages=[ { + "role": "assistant", + "audio": {"id": "id"}, "content": "string", - "role": "developer", + "function_call": { + "arguments": "arguments", + "name": "name", + }, "name": "name", + "refusal": "refusal", + "tool_calls": [ + { + "id": "id", + "function": { + "arguments": "arguments", + "name": "name", + }, + "type": "function", + } + ], } ], options={ @@ -1240,9 +1256,25 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - eval_scores={"foo": 0}, messages=[ { + "role": "assistant", + "audio": {"id": "id"}, "content": "string", - "role": "developer", + "function_call": { + "arguments": "arguments", + "name": "name", + }, "name": "name", + "refusal": "refusal", + "tool_calls": [ + { + "id": "id", + "function": { + "arguments": "arguments", + "name": "name", + }, + "type": "function", + } + ], } ], options={ From 0f0dd538be7aff82b4e7e27f2eb509e0f8f5dec6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 23:18:00 +0000 Subject: [PATCH 211/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9fb51405..19c8465a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: f7b67b502828e6d0ca3944d40d00d89b +openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75 config_hash: 8f6e5c3b064cbb77569a6bf654954a56 From af96aa3c98951fecaa190001320ce2ebb513c268 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:03:15 +0000 Subject: [PATCH 212/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 74 +++++----- src/codex/resources/tlm.py | 128 +++++++++--------- src/codex/types/project_validate_params.py | 58 +++++--- .../query_log_list_by_group_response.py | 46 +++++++ .../query_log_list_groups_response.py | 46 +++++++ .../types/projects/query_log_list_response.py | 46 +++++++ .../projects/query_log_retrieve_response.py | 46 +++++++ ...remediation_list_resolved_logs_response.py | 46 +++++++ src/codex/types/tlm_prompt_params.py | 34 ++--- src/codex/types/tlm_score_params.py | 34 ++--- tests/api_resources/test_projects.py | 24 ++++ tests/api_resources/test_tlm.py | 4 + 13 files changed, 443 insertions(+), 145 deletions(-) diff --git a/.stats.yml b/.stats.yml index 19c8465a..3fdd5d0e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75 +openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d config_hash: 8f6e5c3b064cbb77569a6bf654954a56 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 3a109edf..f82bcd03 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -460,6 +460,7 @@ def validate( quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, + tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -504,17 +505,16 @@ def validate( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -550,12 +550,11 @@ def validate( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -574,6 +573,8 @@ def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. @@ -582,6 +583,9 @@ def validate( rewritten_question: The re-written query if it was provided by the client to Codex from a user to be used instead of the original query. + tools: Tools to use for the LLM call. If not provided, it is assumed no tools were + provided to the LLM. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -620,6 +624,7 @@ def validate( "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, + "tools": tools, }, project_validate_params.ProjectValidateParams, ), @@ -1028,6 +1033,7 @@ async def validate( quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, + tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -1072,17 +1078,16 @@ async def validate( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -1118,12 +1123,11 @@ async def validate( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -1142,6 +1146,8 @@ async def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. @@ -1150,6 +1156,9 @@ async def validate( rewritten_question: The re-written query if it was provided by the client to Codex from a user to be used instead of the original query. + tools: Tools to use for the LLM call. If not provided, it is assumed no tools were + provided to the LLM. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1188,6 +1197,7 @@ async def validate( "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, + "tools": tools, }, project_validate_params.ProjectValidateParams, ), diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 12ff6c0d..c6064ed6 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -79,17 +79,16 @@ def prompt( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -125,12 +124,11 @@ def prompt( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -149,6 +147,8 @@ def prompt( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -217,17 +217,16 @@ def score( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -263,12 +262,11 @@ def score( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -287,6 +285,8 @@ def score( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -371,17 +371,16 @@ async def prompt( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -417,12 +416,11 @@ async def prompt( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -441,6 +439,8 @@ async def prompt( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -509,17 +509,16 @@ async def score( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -555,12 +554,11 @@ async def score( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -579,6 +577,8 @@ async def score( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 081dd2a5..62313671 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -54,6 +54,8 @@ "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", "Options", + "Tool", + "ToolFunction", ] @@ -106,17 +108,16 @@ class ProjectValidateParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -152,12 +153,11 @@ class ProjectValidateParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -175,6 +175,8 @@ class ProjectValidateParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ prompt: Optional[str] @@ -194,6 +196,12 @@ class ProjectValidateParams(TypedDict, total=False): task: Optional[str] + tools: Optional[Iterable[Tool]] + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] @@ -649,8 +657,26 @@ class Options(TypedDict, total=False): num_consistency_samples: int + num_self_reflections: int + reasoning_effort: str similarity_measure: str use_self_reflection: bool + + +class ToolFunction(TypedDict, total=False): + name: Required[str] + + description: str + + parameters: object + + strict: Optional[bool] + + +class Tool(TypedDict, total=False): + function: Required[ToolFunction] + + type: Required[Literal["function"]] diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index ae49b954..b3c774ba 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -16,6 +16,8 @@ "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", "QueryLogsByGroupQueryLogDeterministicGuardrailsResults", + "QueryLogsByGroupQueryLogEvaluatedResponseToolCall", + "QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction", "QueryLogsByGroupQueryLogMessage", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -41,6 +43,8 @@ "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam", "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam", "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", + "QueryLogsByGroupQueryLogTool", + "QueryLogsByGroupQueryLogToolFunction", ] @@ -93,6 +97,20 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel): + id: str + + function: QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction + + type: Literal["function"] + + class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -287,6 +305,22 @@ class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseMod ] +class QueryLogsByGroupQueryLogToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class QueryLogsByGroupQueryLogTool(BaseModel): + function: QueryLogsByGroupQueryLogToolFunction + + type: Literal["function"] + + class QueryLogsByGroupQueryLog(BaseModel): id: str @@ -357,6 +391,12 @@ class QueryLogsByGroupQueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[QueryLogsByGroupQueryLogEvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -383,6 +423,12 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ + class QueryLogsByGroup(BaseModel): query_logs: List[QueryLogsByGroupQueryLog] diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index cc3b208b..6ed4d146 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -14,6 +14,8 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "EvaluatedResponseToolCall", + "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -39,6 +41,8 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "Tool", + "ToolFunction", ] @@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class EvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class EvaluatedResponseToolCall(BaseModel): + id: str + + function: EvaluatedResponseToolCallFunction + + type: Literal["function"] + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] +class ToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class Tool(BaseModel): + function: ToolFunction + + type: Literal["function"] + + class QueryLogListGroupsResponse(BaseModel): id: str @@ -347,6 +381,12 @@ class QueryLogListGroupsResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -372,3 +412,9 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + + tools: Optional[List[Tool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 0778898f..c6737b2f 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -14,6 +14,8 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "EvaluatedResponseToolCall", + "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -39,6 +41,8 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "Tool", + "ToolFunction", ] @@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class EvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class EvaluatedResponseToolCall(BaseModel): + id: str + + function: EvaluatedResponseToolCallFunction + + type: Literal["function"] + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] +class ToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class Tool(BaseModel): + function: ToolFunction + + type: Literal["function"] + + class QueryLogListResponse(BaseModel): id: str @@ -341,6 +375,12 @@ class QueryLogListResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -366,3 +406,9 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + + tools: Optional[List[Tool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 2751ef21..8fd8662e 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -14,6 +14,8 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "EvaluatedResponseToolCall", + "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -39,6 +41,8 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "Tool", + "ToolFunction", ] @@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class EvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class EvaluatedResponseToolCall(BaseModel): + id: str + + function: EvaluatedResponseToolCallFunction + + type: Literal["function"] + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] +class ToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class Tool(BaseModel): + function: ToolFunction + + type: Literal["function"] + + class QueryLogRetrieveResponse(BaseModel): id: str @@ -345,6 +379,12 @@ class QueryLogRetrieveResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -370,3 +410,9 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + + tools: Optional[List[Tool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index d56f9a41..567a0869 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -15,6 +15,8 @@ "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", "QueryLogDeterministicGuardrailsResults", + "QueryLogEvaluatedResponseToolCall", + "QueryLogEvaluatedResponseToolCallFunction", "QueryLogMessage", "QueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -40,6 +42,8 @@ "QueryLogMessageChatCompletionFunctionMessageParam", "QueryLogMessageChatCompletionDeveloperMessageParam", "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", + "QueryLogTool", + "QueryLogToolFunction", ] @@ -92,6 +96,20 @@ class QueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class QueryLogEvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogEvaluatedResponseToolCall(BaseModel): + id: str + + function: QueryLogEvaluatedResponseToolCallFunction + + type: Literal["function"] + + class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -284,6 +302,22 @@ class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): ] +class QueryLogToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class QueryLogTool(BaseModel): + function: QueryLogToolFunction + + type: Literal["function"] + + class QueryLog(BaseModel): id: str @@ -348,6 +382,12 @@ class QueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[QueryLogEvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -374,6 +414,12 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + tools: Optional[List[QueryLogTool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ + class RemediationListResolvedLogsResponse(BaseModel): query_logs: List[QueryLog] diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 3c04bfc4..8749c5ac 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -30,17 +30,16 @@ class TlmPromptParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -76,12 +75,11 @@ class TlmPromptParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -99,6 +97,8 @@ class TlmPromptParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -120,6 +120,8 @@ class Options(TypedDict, total=False): num_consistency_samples: int + num_self_reflections: int + reasoning_effort: str similarity_measure: str diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index 95bcc4c4..4a0a32ad 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -32,17 +32,16 @@ class TlmScoreParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -78,12 +77,11 @@ class TlmScoreParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -101,6 +99,8 @@ class TlmScoreParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -122,6 +122,8 @@ class Options(TypedDict, total=False): num_consistency_samples: int + num_self_reflections: int + reasoning_effort: str similarity_measure: str diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index ae3f4f05..7884db0f 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -622,6 +622,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -630,6 +631,17 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: quality_preset="best", rewritten_question="rewritten_question", task="task", + tools=[ + { + "function": { + "name": "name", + "description": "description", + "parameters": {}, + "strict": True, + }, + "type": "function", + } + ], x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -1284,6 +1296,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -1292,6 +1305,17 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - quality_preset="best", rewritten_question="rewritten_question", task="task", + tools=[ + { + "function": { + "name": "name", + "description": "description", + "parameters": {}, + "strict": True, + }, + "type": "function", + } + ], x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index 41376a46..da0a9ad1 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -38,6 +38,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -96,6 +97,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -160,6 +162,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -218,6 +221,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, From 35dad32d24fd1a7c4815d8e92933cf925b3bcb69 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:04:05 +0000 Subject: [PATCH 213/320] feat(api): remove deprecated endpoint increment_queries --- .stats.yml | 4 +- src/codex/resources/projects/projects.py | 74 +++++----- src/codex/resources/tlm.py | 128 +++++++++--------- src/codex/types/project_validate_params.py | 58 +++----- .../query_log_list_by_group_response.py | 46 ------- .../query_log_list_groups_response.py | 46 ------- .../types/projects/query_log_list_response.py | 46 ------- .../projects/query_log_retrieve_response.py | 46 ------- ...remediation_list_resolved_logs_response.py | 46 ------- src/codex/types/tlm_prompt_params.py | 34 +++-- src/codex/types/tlm_score_params.py | 34 +++-- tests/api_resources/test_projects.py | 24 ---- tests/api_resources/test_tlm.py | 4 - 13 files changed, 146 insertions(+), 444 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3fdd5d0e..031dedf7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d -config_hash: 8f6e5c3b064cbb77569a6bf654954a56 +openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75 +config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index f82bcd03..3a109edf 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -460,7 +460,6 @@ def validate( quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -505,16 +504,17 @@ def validate( The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -550,11 +550,12 @@ def validate( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -573,8 +574,6 @@ def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. - prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. @@ -583,9 +582,6 @@ def validate( rewritten_question: The re-written query if it was provided by the client to Codex from a user to be used instead of the original query. - tools: Tools to use for the LLM call. If not provided, it is assumed no tools were - provided to the LLM. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -624,7 +620,6 @@ def validate( "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, - "tools": tools, }, project_validate_params.ProjectValidateParams, ), @@ -1033,7 +1028,6 @@ async def validate( quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -1078,16 +1072,17 @@ async def validate( The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -1123,11 +1118,12 @@ async def validate( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -1146,8 +1142,6 @@ async def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. - prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. @@ -1156,9 +1150,6 @@ async def validate( rewritten_question: The re-written query if it was provided by the client to Codex from a user to be used instead of the original query. - tools: Tools to use for the LLM call. If not provided, it is assumed no tools were - provided to the LLM. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1197,7 +1188,6 @@ async def validate( "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, - "tools": tools, }, project_validate_params.ProjectValidateParams, ), diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index c6064ed6..12ff6c0d 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -79,16 +79,17 @@ def prompt( The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -124,11 +125,12 @@ def prompt( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -147,8 +149,6 @@ def prompt( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -217,16 +217,17 @@ def score( The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -262,11 +263,12 @@ def score( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -285,8 +287,6 @@ def score( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -371,16 +371,17 @@ async def prompt( The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -416,11 +417,12 @@ async def prompt( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -439,8 +441,6 @@ async def prompt( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -509,16 +509,17 @@ async def score( The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -554,11 +555,12 @@ async def score( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -577,8 +579,6 @@ async def score( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 62313671..081dd2a5 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -54,8 +54,6 @@ "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", "Options", - "Tool", - "ToolFunction", ] @@ -108,16 +106,17 @@ class ProjectValidateParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -153,11 +152,12 @@ class ProjectValidateParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -175,8 +175,6 @@ class ProjectValidateParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ prompt: Optional[str] @@ -196,12 +194,6 @@ class ProjectValidateParams(TypedDict, total=False): task: Optional[str] - tools: Optional[Iterable[Tool]] - """Tools to use for the LLM call. - - If not provided, it is assumed no tools were provided to the LLM. - """ - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] @@ -657,26 +649,8 @@ class Options(TypedDict, total=False): num_consistency_samples: int - num_self_reflections: int - reasoning_effort: str similarity_measure: str use_self_reflection: bool - - -class ToolFunction(TypedDict, total=False): - name: Required[str] - - description: str - - parameters: object - - strict: Optional[bool] - - -class Tool(TypedDict, total=False): - function: Required[ToolFunction] - - type: Required[Literal["function"]] diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index b3c774ba..ae49b954 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -16,8 +16,6 @@ "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", "QueryLogsByGroupQueryLogDeterministicGuardrailsResults", - "QueryLogsByGroupQueryLogEvaluatedResponseToolCall", - "QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction", "QueryLogsByGroupQueryLogMessage", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -43,8 +41,6 @@ "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam", "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam", "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", - "QueryLogsByGroupQueryLogTool", - "QueryLogsByGroupQueryLogToolFunction", ] @@ -97,20 +93,6 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction(BaseModel): - arguments: str - - name: str - - -class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel): - id: str - - function: QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction - - type: Literal["function"] - - class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -305,22 +287,6 @@ class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseMod ] -class QueryLogsByGroupQueryLogToolFunction(BaseModel): - name: str - - description: Optional[str] = None - - parameters: Optional[object] = None - - strict: Optional[bool] = None - - -class QueryLogsByGroupQueryLogTool(BaseModel): - function: QueryLogsByGroupQueryLogToolFunction - - type: Literal["function"] - - class QueryLogsByGroupQueryLog(BaseModel): id: str @@ -391,12 +357,6 @@ class QueryLogsByGroupQueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" - evaluated_response_tool_calls: Optional[List[QueryLogsByGroupQueryLogEvaluatedResponseToolCall]] = None - """Tool calls from the evaluated response, if any. - - Used to log tool calls in the query log. - """ - guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -423,12 +383,6 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None - """Tools to use for the LLM call. - - If not provided, it is assumed no tools were provided to the LLM. - """ - class QueryLogsByGroup(BaseModel): query_logs: List[QueryLogsByGroupQueryLog] diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 6ed4d146..cc3b208b 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -14,8 +14,6 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", - "EvaluatedResponseToolCall", - "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -41,8 +39,6 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", - "Tool", - "ToolFunction", ] @@ -95,20 +91,6 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class EvaluatedResponseToolCallFunction(BaseModel): - arguments: str - - name: str - - -class EvaluatedResponseToolCall(BaseModel): - id: str - - function: EvaluatedResponseToolCallFunction - - type: Literal["function"] - - class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -295,22 +277,6 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] -class ToolFunction(BaseModel): - name: str - - description: Optional[str] = None - - parameters: Optional[object] = None - - strict: Optional[bool] = None - - -class Tool(BaseModel): - function: ToolFunction - - type: Literal["function"] - - class QueryLogListGroupsResponse(BaseModel): id: str @@ -381,12 +347,6 @@ class QueryLogListGroupsResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" - evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None - """Tool calls from the evaluated response, if any. - - Used to log tool calls in the query log. - """ - guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -412,9 +372,3 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - - tools: Optional[List[Tool]] = None - """Tools to use for the LLM call. - - If not provided, it is assumed no tools were provided to the LLM. - """ diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index c6737b2f..0778898f 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -14,8 +14,6 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", - "EvaluatedResponseToolCall", - "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -41,8 +39,6 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", - "Tool", - "ToolFunction", ] @@ -95,20 +91,6 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class EvaluatedResponseToolCallFunction(BaseModel): - arguments: str - - name: str - - -class EvaluatedResponseToolCall(BaseModel): - id: str - - function: EvaluatedResponseToolCallFunction - - type: Literal["function"] - - class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -295,22 +277,6 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] -class ToolFunction(BaseModel): - name: str - - description: Optional[str] = None - - parameters: Optional[object] = None - - strict: Optional[bool] = None - - -class Tool(BaseModel): - function: ToolFunction - - type: Literal["function"] - - class QueryLogListResponse(BaseModel): id: str @@ -375,12 +341,6 @@ class QueryLogListResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" - evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None - """Tool calls from the evaluated response, if any. - - Used to log tool calls in the query log. - """ - guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -406,9 +366,3 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - - tools: Optional[List[Tool]] = None - """Tools to use for the LLM call. - - If not provided, it is assumed no tools were provided to the LLM. - """ diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 8fd8662e..2751ef21 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -14,8 +14,6 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", - "EvaluatedResponseToolCall", - "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -41,8 +39,6 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", - "Tool", - "ToolFunction", ] @@ -95,20 +91,6 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class EvaluatedResponseToolCallFunction(BaseModel): - arguments: str - - name: str - - -class EvaluatedResponseToolCall(BaseModel): - id: str - - function: EvaluatedResponseToolCallFunction - - type: Literal["function"] - - class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -295,22 +277,6 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] -class ToolFunction(BaseModel): - name: str - - description: Optional[str] = None - - parameters: Optional[object] = None - - strict: Optional[bool] = None - - -class Tool(BaseModel): - function: ToolFunction - - type: Literal["function"] - - class QueryLogRetrieveResponse(BaseModel): id: str @@ -379,12 +345,6 @@ class QueryLogRetrieveResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" - evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None - """Tool calls from the evaluated response, if any. - - Used to log tool calls in the query log. - """ - guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -410,9 +370,3 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - - tools: Optional[List[Tool]] = None - """Tools to use for the LLM call. - - If not provided, it is assumed no tools were provided to the LLM. - """ diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 567a0869..d56f9a41 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -15,8 +15,6 @@ "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", "QueryLogDeterministicGuardrailsResults", - "QueryLogEvaluatedResponseToolCall", - "QueryLogEvaluatedResponseToolCallFunction", "QueryLogMessage", "QueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -42,8 +40,6 @@ "QueryLogMessageChatCompletionFunctionMessageParam", "QueryLogMessageChatCompletionDeveloperMessageParam", "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", - "QueryLogTool", - "QueryLogToolFunction", ] @@ -96,20 +92,6 @@ class QueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None -class QueryLogEvaluatedResponseToolCallFunction(BaseModel): - arguments: str - - name: str - - -class QueryLogEvaluatedResponseToolCall(BaseModel): - id: str - - function: QueryLogEvaluatedResponseToolCallFunction - - type: Literal["function"] - - class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -302,22 +284,6 @@ class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): ] -class QueryLogToolFunction(BaseModel): - name: str - - description: Optional[str] = None - - parameters: Optional[object] = None - - strict: Optional[bool] = None - - -class QueryLogTool(BaseModel): - function: QueryLogToolFunction - - type: Literal["function"] - - class QueryLog(BaseModel): id: str @@ -382,12 +348,6 @@ class QueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" - evaluated_response_tool_calls: Optional[List[QueryLogEvaluatedResponseToolCall]] = None - """Tool calls from the evaluated response, if any. - - Used to log tool calls in the query log. - """ - guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -414,12 +374,6 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - tools: Optional[List[QueryLogTool]] = None - """Tools to use for the LLM call. - - If not provided, it is assumed no tools were provided to the LLM. - """ - class RemediationListResolvedLogsResponse(BaseModel): query_logs: List[QueryLog] diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 8749c5ac..3c04bfc4 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -30,16 +30,17 @@ class TlmPromptParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -75,11 +76,12 @@ class TlmPromptParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -97,8 +99,6 @@ class TlmPromptParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -120,8 +120,6 @@ class Options(TypedDict, total=False): num_consistency_samples: int - num_self_reflections: int - reasoning_effort: str similarity_measure: str diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index 4a0a32ad..95bcc4c4 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -32,16 +32,17 @@ class TlmScoreParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. + - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, + `use_self_reflection` = True. This preset improves LLM responses. + - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, + `use_self_reflection` = True. + - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, + `use_self_reflection` = True. + - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, + `use_self_reflection` = False. When using `get_trustworthiness_score()` on + "base" preset, a faster self-reflection is employed. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -77,11 +78,12 @@ class TlmScoreParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts + and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -99,8 +101,6 @@ class TlmScoreParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. - - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -122,8 +122,6 @@ class Options(TypedDict, total=False): num_consistency_samples: int - num_self_reflections: int - reasoning_effort: str similarity_measure: str diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 7884db0f..ae3f4f05 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -622,7 +622,6 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, - "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -631,17 +630,6 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: quality_preset="best", rewritten_question="rewritten_question", task="task", - tools=[ - { - "function": { - "name": "name", - "description": "description", - "parameters": {}, - "strict": True, - }, - "type": "function", - } - ], x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -1296,7 +1284,6 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, - "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -1305,17 +1292,6 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - quality_preset="best", rewritten_question="rewritten_question", task="task", - tools=[ - { - "function": { - "name": "name", - "description": "description", - "parameters": {}, - "strict": True, - }, - "type": "function", - } - ], x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index da0a9ad1..41376a46 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -38,7 +38,6 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, - "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -97,7 +96,6 @@ def test_method_score_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, - "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -162,7 +160,6 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, - "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -221,7 +218,6 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, - "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, From 63ecb44534f3e4ae94ac9ee99ab9f89cc88295ce Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:18:00 +0000 Subject: [PATCH 214/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 74 +++++----- src/codex/resources/tlm.py | 128 +++++++++--------- src/codex/types/project_validate_params.py | 58 +++++--- .../query_log_list_by_group_response.py | 46 +++++++ .../query_log_list_groups_response.py | 46 +++++++ .../types/projects/query_log_list_response.py | 46 +++++++ .../projects/query_log_retrieve_response.py | 46 +++++++ ...remediation_list_resolved_logs_response.py | 46 +++++++ src/codex/types/tlm_prompt_params.py | 34 ++--- src/codex/types/tlm_score_params.py | 34 ++--- tests/api_resources/test_projects.py | 24 ++++ tests/api_resources/test_tlm.py | 4 + 13 files changed, 443 insertions(+), 145 deletions(-) diff --git a/.stats.yml b/.stats.yml index 031dedf7..4f2aa488 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 57e29e33aec4bbc20171ec3128594e75 +openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 3a109edf..f82bcd03 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -460,6 +460,7 @@ def validate( quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, + tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -504,17 +505,16 @@ def validate( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -550,12 +550,11 @@ def validate( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -574,6 +573,8 @@ def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. @@ -582,6 +583,9 @@ def validate( rewritten_question: The re-written query if it was provided by the client to Codex from a user to be used instead of the original query. + tools: Tools to use for the LLM call. If not provided, it is assumed no tools were + provided to the LLM. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -620,6 +624,7 @@ def validate( "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, + "tools": tools, }, project_validate_params.ProjectValidateParams, ), @@ -1028,6 +1033,7 @@ async def validate( quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, + tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -1072,17 +1078,16 @@ async def validate( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -1118,12 +1123,11 @@ async def validate( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -1142,6 +1146,8 @@ async def validate( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. @@ -1150,6 +1156,9 @@ async def validate( rewritten_question: The re-written query if it was provided by the client to Codex from a user to be used instead of the original query. + tools: Tools to use for the LLM call. If not provided, it is assumed no tools were + provided to the LLM. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1188,6 +1197,7 @@ async def validate( "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, + "tools": tools, }, project_validate_params.ProjectValidateParams, ), diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 12ff6c0d..c6064ed6 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -79,17 +79,16 @@ def prompt( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -125,12 +124,11 @@ def prompt( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -149,6 +147,8 @@ def prompt( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -217,17 +217,16 @@ def score( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -263,12 +262,11 @@ def score( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -287,6 +285,8 @@ def score( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -371,17 +371,16 @@ async def prompt( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -417,12 +416,11 @@ async def prompt( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -441,6 +439,8 @@ async def prompt( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers @@ -509,17 +509,16 @@ async def score( The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -555,12 +554,11 @@ async def score( strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -579,6 +577,8 @@ async def score( - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. extra_headers: Send extra headers diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 081dd2a5..62313671 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -54,6 +54,8 @@ "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", "Options", + "Tool", + "ToolFunction", ] @@ -106,17 +108,16 @@ class ProjectValidateParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -152,12 +153,11 @@ class ProjectValidateParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -175,6 +175,8 @@ class ProjectValidateParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ prompt: Optional[str] @@ -194,6 +196,12 @@ class ProjectValidateParams(TypedDict, total=False): task: Optional[str] + tools: Optional[Iterable[Tool]] + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] @@ -649,8 +657,26 @@ class Options(TypedDict, total=False): num_consistency_samples: int + num_self_reflections: int + reasoning_effort: str similarity_measure: str use_self_reflection: bool + + +class ToolFunction(TypedDict, total=False): + name: Required[str] + + description: str + + parameters: object + + strict: Optional[bool] + + +class Tool(TypedDict, total=False): + function: Required[ToolFunction] + + type: Required[Literal["function"]] diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index ae49b954..b3c774ba 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -16,6 +16,8 @@ "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", "QueryLogsByGroupQueryLogDeterministicGuardrailsResults", + "QueryLogsByGroupQueryLogEvaluatedResponseToolCall", + "QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction", "QueryLogsByGroupQueryLogMessage", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -41,6 +43,8 @@ "QueryLogsByGroupQueryLogMessageChatCompletionFunctionMessageParam", "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam", "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", + "QueryLogsByGroupQueryLogTool", + "QueryLogsByGroupQueryLogToolFunction", ] @@ -93,6 +97,20 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel): + id: str + + function: QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction + + type: Literal["function"] + + class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -287,6 +305,22 @@ class QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParam(BaseMod ] +class QueryLogsByGroupQueryLogToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class QueryLogsByGroupQueryLogTool(BaseModel): + function: QueryLogsByGroupQueryLogToolFunction + + type: Literal["function"] + + class QueryLogsByGroupQueryLog(BaseModel): id: str @@ -357,6 +391,12 @@ class QueryLogsByGroupQueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[QueryLogsByGroupQueryLogEvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -383,6 +423,12 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ + class QueryLogsByGroup(BaseModel): query_logs: List[QueryLogsByGroupQueryLog] diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index cc3b208b..6ed4d146 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -14,6 +14,8 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "EvaluatedResponseToolCall", + "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -39,6 +41,8 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "Tool", + "ToolFunction", ] @@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class EvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class EvaluatedResponseToolCall(BaseModel): + id: str + + function: EvaluatedResponseToolCallFunction + + type: Literal["function"] + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] +class ToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class Tool(BaseModel): + function: ToolFunction + + type: Literal["function"] + + class QueryLogListGroupsResponse(BaseModel): id: str @@ -347,6 +381,12 @@ class QueryLogListGroupsResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -372,3 +412,9 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + + tools: Optional[List[Tool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 0778898f..c6737b2f 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -14,6 +14,8 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "EvaluatedResponseToolCall", + "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -39,6 +41,8 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "Tool", + "ToolFunction", ] @@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class EvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class EvaluatedResponseToolCall(BaseModel): + id: str + + function: EvaluatedResponseToolCallFunction + + type: Literal["function"] + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] +class ToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class Tool(BaseModel): + function: ToolFunction + + type: Literal["function"] + + class QueryLogListResponse(BaseModel): id: str @@ -341,6 +375,12 @@ class QueryLogListResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -366,3 +406,9 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + + tools: Optional[List[Tool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 2751ef21..8fd8662e 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -14,6 +14,8 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "EvaluatedResponseToolCall", + "EvaluatedResponseToolCallFunction", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -39,6 +41,8 @@ "MessageChatCompletionFunctionMessageParam", "MessageChatCompletionDeveloperMessageParam", "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "Tool", + "ToolFunction", ] @@ -91,6 +95,20 @@ class DeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class EvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class EvaluatedResponseToolCall(BaseModel): + id: str + + function: EvaluatedResponseToolCallFunction + + type: Literal["function"] + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -277,6 +295,22 @@ class MessageChatCompletionDeveloperMessageParam(BaseModel): ] +class ToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class Tool(BaseModel): + function: ToolFunction + + type: Literal["function"] + + class QueryLogRetrieveResponse(BaseModel): id: str @@ -345,6 +379,12 @@ class QueryLogRetrieveResponse(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[EvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -370,3 +410,9 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + + tools: Optional[List[Tool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index d56f9a41..567a0869 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -15,6 +15,8 @@ "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", "QueryLogDeterministicGuardrailsResults", + "QueryLogEvaluatedResponseToolCall", + "QueryLogEvaluatedResponseToolCallFunction", "QueryLogMessage", "QueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -40,6 +42,8 @@ "QueryLogMessageChatCompletionFunctionMessageParam", "QueryLogMessageChatCompletionDeveloperMessageParam", "QueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", + "QueryLogTool", + "QueryLogToolFunction", ] @@ -92,6 +96,20 @@ class QueryLogDeterministicGuardrailsResults(BaseModel): matches: Optional[List[str]] = None +class QueryLogEvaluatedResponseToolCallFunction(BaseModel): + arguments: str + + name: str + + +class QueryLogEvaluatedResponseToolCall(BaseModel): + id: str + + function: QueryLogEvaluatedResponseToolCallFunction + + type: Literal["function"] + + class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -284,6 +302,22 @@ class QueryLogMessageChatCompletionDeveloperMessageParam(BaseModel): ] +class QueryLogToolFunction(BaseModel): + name: str + + description: Optional[str] = None + + parameters: Optional[object] = None + + strict: Optional[bool] = None + + +class QueryLogTool(BaseModel): + function: QueryLogToolFunction + + type: Literal["function"] + + class QueryLog(BaseModel): id: str @@ -348,6 +382,12 @@ class QueryLog(BaseModel): evaluated_response: Optional[str] = None """The response being evaluated from the RAG system (before any remediation)""" + evaluated_response_tool_calls: Optional[List[QueryLogEvaluatedResponseToolCall]] = None + """Tool calls from the evaluated response, if any. + + Used to log tool calls in the query log. + """ + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -374,6 +414,12 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + tools: Optional[List[QueryLogTool]] = None + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ + class RemediationListResolvedLogsResponse(BaseModel): query_logs: List[QueryLog] diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 3c04bfc4..8749c5ac 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -30,17 +30,16 @@ class TlmPromptParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -76,12 +75,11 @@ class TlmPromptParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -99,6 +97,8 @@ class TlmPromptParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -120,6 +120,8 @@ class Options(TypedDict, total=False): num_consistency_samples: int + num_self_reflections: int + reasoning_effort: str similarity_measure: str diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index 95bcc4c4..4a0a32ad 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -32,17 +32,16 @@ class TlmScoreParams(TypedDict, total=False): The default values corresponding to each quality preset are: - - **best:** `num_candidate_responses` = 6, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **high:** `num_candidate_responses` = 4, `num_consistency_samples` = 8, - `use_self_reflection` = True. This preset improves LLM responses. - - **medium:** `num_candidate_responses` = 1, `num_consistency_samples` = 8, - `use_self_reflection` = True. - - **low:** `num_candidate_responses` = 1, `num_consistency_samples` = 4, - `use_self_reflection` = True. - - **base:** `num_candidate_responses` = 1, `num_consistency_samples` = 0, - `use_self_reflection` = False. When using `get_trustworthiness_score()` on - "base" preset, a faster self-reflection is employed. + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. You can set custom values for these @@ -78,12 +77,11 @@ class TlmScoreParams(TypedDict, total=False): strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - use_self_reflection (bool, default = `True`): whether the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - Setting this False disables reflection and will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts - and catches responses that are noticeably incorrect/bad upon further analysis. + num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. + The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "semantic"): how the + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), @@ -101,6 +99,8 @@ class TlmScoreParams(TypedDict, total=False): The expected input format is a list of dictionaries, where each dictionary has the following keys: - name: Name of the evaluation criteria. - criteria: Instructions specifying the evaluation criteria. + + use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -122,6 +122,8 @@ class Options(TypedDict, total=False): num_consistency_samples: int + num_self_reflections: int + reasoning_effort: str similarity_measure: str diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index ae3f4f05..7884db0f 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -622,6 +622,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -630,6 +631,17 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: quality_preset="best", rewritten_question="rewritten_question", task="task", + tools=[ + { + "function": { + "name": "name", + "description": "description", + "parameters": {}, + "strict": True, + }, + "type": "function", + } + ], x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -1284,6 +1296,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -1292,6 +1305,17 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - quality_preset="best", rewritten_question="rewritten_question", task="task", + tools=[ + { + "function": { + "name": "name", + "description": "description", + "parameters": {}, + "strict": True, + }, + "type": "function", + } + ], x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index 41376a46..da0a9ad1 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -38,6 +38,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -96,6 +97,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None: "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -160,6 +162,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, @@ -218,6 +221,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N "model": "model", "num_candidate_responses": 0, "num_consistency_samples": 0, + "num_self_reflections": 0, "reasoning_effort": "reasoning_effort", "similarity_measure": "similarity_measure", "use_self_reflection": True, From e132c148f2f60fde5e152b544ca62f62856df95a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:21:09 +0000 Subject: [PATCH 215/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1c0bb885..380b6f91 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.23" + ".": "0.1.0-alpha.24" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0e315f1b..6a941cf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.23" +version = "0.1.0-alpha.24" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 18f2dcb7..e020cb91 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.23" # x-release-please-version +__version__ = "0.1.0-alpha.24" # x-release-please-version From 8bdd5d3687dbc60f6193d409ffac8524505b8480 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 23:18:01 +0000 Subject: [PATCH 216/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 4 ++-- src/codex/types/project_invite_sme_params.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4f2aa488..9c3265d2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 49989625bf633c5fdb3e11140f788f2d +openapi_spec_hash: c37472afd4b354d9a4e358b9dc6fd1a8 config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index f82bcd03..83ff4421 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -352,7 +352,7 @@ def invite_sme( project_id: str, *, email: str, - page_type: Literal["query_log", "remediation"], + page_type: Literal["query_log", "remediation", "prioritized_issue"], url_query_string: 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. @@ -925,7 +925,7 @@ async def invite_sme( project_id: str, *, email: str, - page_type: Literal["query_log", "remediation"], + page_type: Literal["query_log", "remediation", "prioritized_issue"], url_query_string: 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. diff --git a/src/codex/types/project_invite_sme_params.py b/src/codex/types/project_invite_sme_params.py index f2694632..974ef7c3 100644 --- a/src/codex/types/project_invite_sme_params.py +++ b/src/codex/types/project_invite_sme_params.py @@ -10,6 +10,6 @@ class ProjectInviteSmeParams(TypedDict, total=False): email: Required[str] - page_type: Required[Literal["query_log", "remediation"]] + page_type: Required[Literal["query_log", "remediation", "prioritized_issue"]] url_query_string: Required[str] From 47c55ecf735ba66b6e69351413b66e3cbdf569ef Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:17:53 +0000 Subject: [PATCH 217/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 24 +++++++++++++++++++ .../query_log_list_by_group_params.py | 3 +++ .../query_log_list_by_group_response.py | 6 +++++ .../projects/query_log_list_groups_params.py | 3 +++ .../query_log_list_groups_response.py | 2 ++ .../types/projects/query_log_list_params.py | 3 +++ .../types/projects/query_log_list_response.py | 2 ++ .../projects/query_log_retrieve_response.py | 2 ++ ...remediation_list_resolved_logs_response.py | 2 ++ .../api_resources/projects/test_query_logs.py | 6 +++++ 11 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9c3265d2..6adad987 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: c37472afd4b354d9a4e358b9dc6fd1a8 +openapi_spec_hash: 6b4e0d5289d21e9bcda264caea7f03d8 config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 6fa490e8..268d3ac1 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -109,6 +109,7 @@ def list( ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -135,6 +136,8 @@ def list( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + tool_call_names: Filter by names of tools called in the assistant response + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -168,6 +171,7 @@ def list( "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, + "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, }, query_log_list_params.QueryLogListParams, @@ -196,6 +200,7 @@ def list_by_group( | NotGiven = NOT_GIVEN, remediation_ids: List[str] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -226,6 +231,8 @@ def list_by_group( remediation_ids: List of groups to list child logs for + tool_call_names: Filter by names of tools called in the assistant response + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -260,6 +267,7 @@ def list_by_group( "primary_eval_issue": primary_eval_issue, "remediation_ids": remediation_ids, "sort": sort, + "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, }, query_log_list_by_group_params.QueryLogListByGroupParams, @@ -288,6 +296,7 @@ def list_groups( | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -316,6 +325,8 @@ def list_groups( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + tool_call_names: Filter by names of tools called in the assistant response + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -350,6 +361,7 @@ def list_groups( "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, + "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, }, query_log_list_groups_params.QueryLogListGroupsParams, @@ -469,6 +481,7 @@ def list( ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -495,6 +508,8 @@ def list( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + tool_call_names: Filter by names of tools called in the assistant response + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -528,6 +543,7 @@ def list( "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, + "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, }, query_log_list_params.QueryLogListParams, @@ -556,6 +572,7 @@ async def list_by_group( | NotGiven = NOT_GIVEN, remediation_ids: List[str] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -586,6 +603,8 @@ async def list_by_group( remediation_ids: List of groups to list child logs for + tool_call_names: Filter by names of tools called in the assistant response + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -620,6 +639,7 @@ async def list_by_group( "primary_eval_issue": primary_eval_issue, "remediation_ids": remediation_ids, "sort": sort, + "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, }, query_log_list_by_group_params.QueryLogListByGroupParams, @@ -648,6 +668,7 @@ def list_groups( | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -676,6 +697,8 @@ def list_groups( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + tool_call_names: Filter by names of tools called in the assistant response + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -710,6 +733,7 @@ def list_groups( "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "sort": sort, + "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, }, query_log_list_groups_params.QueryLogListGroupsParams, diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 90bd3867..74edd4b2 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -49,5 +49,8 @@ class QueryLogListByGroupParams(TypedDict, total=False): sort: Optional[Literal["created_at", "primary_eval_issue_score"]] + tool_call_names: Optional[List[str]] + """Filter by names of tools called in the assistant response""" + was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index b3c774ba..fc33cdeb 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -358,6 +358,8 @@ class QueryLogsByGroupQueryLog(BaseModel): remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] + tool_call_names: Optional[List[str]] = None + was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" @@ -438,5 +440,9 @@ class QueryLogsByGroup(BaseModel): class QueryLogListByGroupResponse(BaseModel): custom_metadata_columns: List[str] + """Columns of the custom metadata""" query_logs_by_group: Dict[str, QueryLogsByGroup] + + tool_names: Optional[List[str]] = None + """Names of the tools available in queries""" diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index f75ee299..5461b541 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -46,5 +46,8 @@ class QueryLogListGroupsParams(TypedDict, total=False): sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] + tool_call_names: Optional[List[str]] + """Filter by names of tools called in the assistant response""" + was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 6ed4d146..60545aaa 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -346,6 +346,8 @@ class QueryLogListGroupsResponse(BaseModel): remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] + tool_call_names: Optional[List[str]] = None + total_count: int was_cache_hit: Optional[bool] = None diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 5892d3c9..2f198acb 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -43,5 +43,8 @@ class QueryLogListParams(TypedDict, total=False): sort: Optional[Literal["created_at", "primary_eval_issue_score"]] + tool_call_names: Optional[List[str]] + """Filter by names of tools called in the assistant response""" + was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index c6737b2f..b56d43d3 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -342,6 +342,8 @@ class QueryLogListResponse(BaseModel): remediation_id: str + tool_call_names: Optional[List[str]] = None + was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 8fd8662e..b9be8d6d 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -346,6 +346,8 @@ class QueryLogRetrieveResponse(BaseModel): remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] + tool_call_names: Optional[List[str]] = None + was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 567a0869..ed764766 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -349,6 +349,8 @@ class QueryLog(BaseModel): remediation_id: str + tool_call_names: Optional[List[str]] = None + was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index cd4cd7d2..d98cdc9c 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -106,6 +106,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", + tool_call_names=["string"], was_cache_hit=True, ) assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @@ -170,6 +171,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], sort="created_at", + tool_call_names=["string"], was_cache_hit=True, ) assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) @@ -233,6 +235,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", + tool_call_names=["string"], was_cache_hit=True, ) assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @@ -405,6 +408,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", + tool_call_names=["string"], was_cache_hit=True, ) assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @@ -469,6 +473,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], sort="created_at", + tool_call_names=["string"], was_cache_hit=True, ) assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) @@ -532,6 +537,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex passed_evals=["string"], primary_eval_issue=["hallucination"], sort="created_at", + tool_call_names=["string"], was_cache_hit=True, ) assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) From b57b54b1b29e7bf6e4c82208527f65950c17a114 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 03:41:41 +0000 Subject: [PATCH 218/320] feat(client): support file upload requests --- src/codex/_base_client.py | 5 ++++- src/codex/_files.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 6da89f6c..870a4729 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -532,7 +532,10 @@ def _build_request( is_body_allowed = options.method.lower() != "get" if is_body_allowed: - kwargs["json"] = json_data if is_given(json_data) else None + if isinstance(json_data, bytes): + kwargs["content"] = json_data + else: + kwargs["json"] = json_data if is_given(json_data) else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/codex/_files.py b/src/codex/_files.py index 715cc207..cc14c14f 100644 --- a/src/codex/_files.py +++ b/src/codex/_files.py @@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], _read_file_content(file[1]), *file[2:]) + return (file[0], read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -def _read_file_content(file: FileContent) -> HttpxFileContent: +def read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return pathlib.Path(file).read_bytes() return file @@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], await _async_read_file_content(file[1]), *file[2:]) + return (file[0], await async_read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -async def _async_read_file_content(file: FileContent) -> HttpxFileContent: +async def async_read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return await anyio.Path(file).read_bytes() From 4bee8fb085790a6dcaf4e9cfde9e09f77493b739 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 17:17:52 +0000 Subject: [PATCH 219/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 24 +++++++++++++++++++ .../query_log_list_by_group_params.py | 3 +++ .../projects/query_log_list_groups_params.py | 3 +++ .../query_log_list_groups_response.py | 3 +++ .../types/projects/query_log_list_params.py | 3 +++ .../api_resources/projects/test_query_logs.py | 6 +++++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 6adad987..761389b9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 6b4e0d5289d21e9bcda264caea7f03d8 +openapi_spec_hash: 928298b295109c995a0b3a8679b441b7 config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 268d3ac1..45277433 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -100,6 +100,7 @@ def list( custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, + has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, @@ -132,6 +133,8 @@ def list( guardrailed: Filter by guardrailed status + has_tool_calls: Filter by whether the query log has tool calls + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -165,6 +168,7 @@ def list( "custom_metadata": custom_metadata, "failed_evals": failed_evals, "guardrailed": guardrailed, + "has_tool_calls": has_tool_calls, "limit": limit, "offset": offset, "order": order, @@ -189,6 +193,7 @@ def list_by_group( custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, + has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, @@ -223,6 +228,8 @@ def list_by_group( guardrailed: Filter by guardrailed status + has_tool_calls: Filter by whether the query log has tool calls + needs_review: Filter logs that need review passed_evals: Filter by evals that passed @@ -259,6 +266,7 @@ def list_by_group( "custom_metadata": custom_metadata, "failed_evals": failed_evals, "guardrailed": guardrailed, + "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, "offset": offset, @@ -285,6 +293,7 @@ def list_groups( custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, + has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, @@ -319,6 +328,8 @@ def list_groups( guardrailed: Filter by guardrailed status + has_tool_calls: Filter by whether the query log has tool calls + needs_review: Filter log groups that need review passed_evals: Filter by evals that passed @@ -354,6 +365,7 @@ def list_groups( "custom_metadata": custom_metadata, "failed_evals": failed_evals, "guardrailed": guardrailed, + "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, "offset": offset, @@ -472,6 +484,7 @@ def list( custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, + has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, @@ -504,6 +517,8 @@ def list( guardrailed: Filter by guardrailed status + has_tool_calls: Filter by whether the query log has tool calls + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -537,6 +552,7 @@ def list( "custom_metadata": custom_metadata, "failed_evals": failed_evals, "guardrailed": guardrailed, + "has_tool_calls": has_tool_calls, "limit": limit, "offset": offset, "order": order, @@ -561,6 +577,7 @@ async def list_by_group( custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, + has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, @@ -595,6 +612,8 @@ async def list_by_group( guardrailed: Filter by guardrailed status + has_tool_calls: Filter by whether the query log has tool calls + needs_review: Filter logs that need review passed_evals: Filter by evals that passed @@ -631,6 +650,7 @@ async def list_by_group( "custom_metadata": custom_metadata, "failed_evals": failed_evals, "guardrailed": guardrailed, + "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, "offset": offset, @@ -657,6 +677,7 @@ def list_groups( custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, + has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, @@ -691,6 +712,8 @@ def list_groups( guardrailed: Filter by guardrailed status + has_tool_calls: Filter by whether the query log has tool calls + needs_review: Filter log groups that need review passed_evals: Filter by evals that passed @@ -726,6 +749,7 @@ def list_groups( "custom_metadata": custom_metadata, "failed_evals": failed_evals, "guardrailed": guardrailed, + "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, "offset": offset, diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 74edd4b2..0fbb2804 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -27,6 +27,9 @@ class QueryLogListByGroupParams(TypedDict, total=False): guardrailed: Optional[bool] """Filter by guardrailed status""" + has_tool_calls: Optional[bool] + """Filter by whether the query log has tool calls""" + limit: int needs_review: Optional[bool] diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index 5461b541..6adefdf5 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -27,6 +27,9 @@ class QueryLogListGroupsParams(TypedDict, total=False): guardrailed: Optional[bool] """Filter by guardrailed status""" + has_tool_calls: Optional[bool] + """Filter by whether the query log has tool calls""" + limit: int needs_review: Optional[bool] diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 60545aaa..7b2d44c9 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -314,6 +314,9 @@ class Tool(BaseModel): class QueryLogListGroupsResponse(BaseModel): id: str + any_escalated: bool + """Whether any query log in the group was escalated""" + created_at: datetime formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 2f198acb..02c1707b 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -27,6 +27,9 @@ class QueryLogListParams(TypedDict, total=False): guardrailed: Optional[bool] """Filter by guardrailed status""" + has_tool_calls: Optional[bool] + """Filter by whether the query log has tool calls""" + limit: int offset: int diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index d98cdc9c..3e0a96fa 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -100,6 +100,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: custom_metadata="custom_metadata", failed_evals=["string"], guardrailed=True, + has_tool_calls=True, limit=1, offset=0, order="asc", @@ -163,6 +164,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: custom_metadata="custom_metadata", failed_evals=["string"], guardrailed=True, + has_tool_calls=True, limit=1, needs_review=True, offset=0, @@ -228,6 +230,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: custom_metadata="custom_metadata", failed_evals=["string"], guardrailed=True, + has_tool_calls=True, limit=1, needs_review=True, offset=0, @@ -402,6 +405,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No custom_metadata="custom_metadata", failed_evals=["string"], guardrailed=True, + has_tool_calls=True, limit=1, offset=0, order="asc", @@ -465,6 +469,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod custom_metadata="custom_metadata", failed_evals=["string"], guardrailed=True, + has_tool_calls=True, limit=1, needs_review=True, offset=0, @@ -530,6 +535,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex custom_metadata="custom_metadata", failed_evals=["string"], guardrailed=True, + has_tool_calls=True, limit=1, needs_review=True, offset=0, From 43885ef093390608810614df222c61ac9fabfc04 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 04:30:12 +0000 Subject: [PATCH 220/320] chore(internal): fix ruff target version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6a941cf3..cc023e90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,7 +159,7 @@ reportPrivateUsage = false [tool.ruff] line-length = 120 output-format = "grouped" -target-version = "py37" +target-version = "py38" [tool.ruff.format] docstring-code-format = true From 09d3441a8e1eae47a5a39b6c4ef4440ea0ca4ff1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:17:54 +0000 Subject: [PATCH 221/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 4 ++-- src/codex/types/project_validate_params.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 761389b9..3aa68e69 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 928298b295109c995a0b3a8679b441b7 +openapi_spec_hash: d01153406f196329a5d5d1efd8f3e50e config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 83ff4421..d21f7937 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -449,7 +449,7 @@ def validate( context: str, query: str, response: project_validate_params.Response, - use_llm_matching: bool | NotGiven = NOT_GIVEN, + use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, @@ -1022,7 +1022,7 @@ async def validate( context: str, query: str, response: project_validate_params.Response, - use_llm_matching: bool | NotGiven = NOT_GIVEN, + use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN, constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 62313671..86c10495 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -66,7 +66,7 @@ class ProjectValidateParams(TypedDict, total=False): response: Required[Response] - use_llm_matching: bool + use_llm_matching: Optional[bool] constrain_outputs: Optional[List[str]] From 705ef5ab013b45b2ef0749b8a9d3bb0e13013ce9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 03:09:37 +0000 Subject: [PATCH 222/320] chore: update @stainless-api/prism-cli to v5.15.0 --- scripts/mock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mock b/scripts/mock index d2814ae6..0b28f6ea 100755 --- a/scripts/mock +++ b/scripts/mock @@ -21,7 +21,7 @@ echo "==> Starting mock server with URL ${URL}" # Run prism mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & # Wait for server to come online echo -n "Waiting for server" @@ -37,5 +37,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" fi From 58e840d518d63c1ae838b1e8781aef4590204e6f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 03:12:54 +0000 Subject: [PATCH 223/320] chore(internal): update comment in script --- scripts/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test b/scripts/test index 2b878456..dbeda2d2 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! prism_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the prism command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" echo exit 1 From 81619adf7a2fe293f8bc83cc2b25ce57b79cb893 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:08:43 +0000 Subject: [PATCH 224/320] chore(internal): codegen related update --- .../billing/test_card_details.py | 16 +- .../billing/test_plan_details.py | 16 +- .../billing/test_setup_intent.py | 16 +- .../organizations/test_billing.py | 32 +-- .../projects/test_access_keys.py | 116 +++++------ tests/api_resources/projects/test_evals.py | 96 ++++----- .../api_resources/projects/test_query_logs.py | 92 ++++----- .../projects/test_remediations.py | 184 +++++++++--------- tests/api_resources/test_health.py | 24 +-- tests/api_resources/test_organizations.py | 48 ++--- tests/api_resources/test_projects.py | 156 +++++++-------- tests/api_resources/test_tlm.py | 32 +-- tests/api_resources/test_users.py | 16 +- .../users/myself/test_api_key.py | 24 +-- .../users/myself/test_organizations.py | 12 +- tests/api_resources/users/test_myself.py | 12 +- .../api_resources/users/test_verification.py | 12 +- 17 files changed, 452 insertions(+), 452 deletions(-) diff --git a/tests/api_resources/organizations/billing/test_card_details.py b/tests/api_resources/organizations/billing/test_card_details.py index 3a034833..e4468456 100644 --- a/tests/api_resources/organizations/billing/test_card_details.py +++ b/tests/api_resources/organizations/billing/test_card_details.py @@ -17,7 +17,7 @@ class TestCardDetails: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: card_detail = client.organizations.billing.card_details.retrieve( @@ -25,7 +25,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.organizations.billing.card_details.with_raw_response.retrieve( @@ -37,7 +37,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: card_detail = response.parse() assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.organizations.billing.card_details.with_streaming_response.retrieve( @@ -51,7 +51,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -65,7 +65,7 @@ class TestAsyncCardDetails: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: card_detail = await async_client.organizations.billing.card_details.retrieve( @@ -73,7 +73,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.card_details.with_raw_response.retrieve( @@ -85,7 +85,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: card_detail = await response.parse() assert_matches_type(Optional[OrganizationBillingCardDetails], card_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.card_details.with_streaming_response.retrieve( @@ -99,7 +99,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): diff --git a/tests/api_resources/organizations/billing/test_plan_details.py b/tests/api_resources/organizations/billing/test_plan_details.py index 76d9732e..1e3c36fb 100644 --- a/tests/api_resources/organizations/billing/test_plan_details.py +++ b/tests/api_resources/organizations/billing/test_plan_details.py @@ -17,7 +17,7 @@ class TestPlanDetails: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: plan_detail = client.organizations.billing.plan_details.retrieve( @@ -25,7 +25,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.organizations.billing.plan_details.with_raw_response.retrieve( @@ -37,7 +37,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: plan_detail = response.parse() assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.organizations.billing.plan_details.with_streaming_response.retrieve( @@ -51,7 +51,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -65,7 +65,7 @@ class TestAsyncPlanDetails: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: plan_detail = await async_client.organizations.billing.plan_details.retrieve( @@ -73,7 +73,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.plan_details.with_raw_response.retrieve( @@ -85,7 +85,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: plan_detail = await response.parse() assert_matches_type(OrganizationBillingPlanDetails, plan_detail, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.plan_details.with_streaming_response.retrieve( @@ -99,7 +99,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): diff --git a/tests/api_resources/organizations/billing/test_setup_intent.py b/tests/api_resources/organizations/billing/test_setup_intent.py index 49d80b0d..edc3372b 100644 --- a/tests/api_resources/organizations/billing/test_setup_intent.py +++ b/tests/api_resources/organizations/billing/test_setup_intent.py @@ -17,7 +17,7 @@ class TestSetupIntent: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Codex) -> None: setup_intent = client.organizations.billing.setup_intent.create( @@ -25,7 +25,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.organizations.billing.setup_intent.with_raw_response.create( @@ -37,7 +37,7 @@ def test_raw_response_create(self, client: Codex) -> None: setup_intent = response.parse() assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.organizations.billing.setup_intent.with_streaming_response.create( @@ -51,7 +51,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_create(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -65,7 +65,7 @@ class TestAsyncSetupIntent: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: setup_intent = await async_client.organizations.billing.setup_intent.create( @@ -73,7 +73,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.setup_intent.with_raw_response.create( @@ -85,7 +85,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: setup_intent = await response.parse() assert_matches_type(OrganizationBillingSetupIntent, setup_intent, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.setup_intent.with_streaming_response.create( @@ -99,7 +99,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_create(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): diff --git a/tests/api_resources/organizations/test_billing.py b/tests/api_resources/organizations/test_billing.py index 237562b5..f13fa304 100644 --- a/tests/api_resources/organizations/test_billing.py +++ b/tests/api_resources/organizations/test_billing.py @@ -17,7 +17,7 @@ class TestBilling: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_invoices(self, client: Codex) -> None: billing = client.organizations.billing.invoices( @@ -25,7 +25,7 @@ def test_method_invoices(self, client: Codex) -> None: ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_invoices(self, client: Codex) -> None: response = client.organizations.billing.with_raw_response.invoices( @@ -37,7 +37,7 @@ def test_raw_response_invoices(self, client: Codex) -> None: billing = response.parse() assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_invoices(self, client: Codex) -> None: with client.organizations.billing.with_streaming_response.invoices( @@ -51,7 +51,7 @@ def test_streaming_response_invoices(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_invoices(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -59,7 +59,7 @@ def test_path_params_invoices(self, client: Codex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_usage(self, client: Codex) -> None: billing = client.organizations.billing.usage( @@ -67,7 +67,7 @@ def test_method_usage(self, client: Codex) -> None: ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_usage(self, client: Codex) -> None: response = client.organizations.billing.with_raw_response.usage( @@ -79,7 +79,7 @@ def test_raw_response_usage(self, client: Codex) -> None: billing = response.parse() assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_usage(self, client: Codex) -> None: with client.organizations.billing.with_streaming_response.usage( @@ -93,7 +93,7 @@ def test_streaming_response_usage(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_usage(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -107,7 +107,7 @@ class TestAsyncBilling: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_invoices(self, async_client: AsyncCodex) -> None: billing = await async_client.organizations.billing.invoices( @@ -115,7 +115,7 @@ async def test_method_invoices(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.with_raw_response.invoices( @@ -127,7 +127,7 @@ async def test_raw_response_invoices(self, async_client: AsyncCodex) -> None: billing = await response.parse() assert_matches_type(OrganizationBillingInvoicesSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.with_streaming_response.invoices( @@ -141,7 +141,7 @@ async def test_streaming_response_invoices(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_invoices(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -149,7 +149,7 @@ async def test_path_params_invoices(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_usage(self, async_client: AsyncCodex) -> None: billing = await async_client.organizations.billing.usage( @@ -157,7 +157,7 @@ async def test_method_usage(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.billing.with_raw_response.usage( @@ -169,7 +169,7 @@ async def test_raw_response_usage(self, async_client: AsyncCodex) -> None: billing = await response.parse() assert_matches_type(OrganizationBillingUsageSchema, billing, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: async with async_client.organizations.billing.with_streaming_response.usage( @@ -183,7 +183,7 @@ async def test_streaming_response_usage(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_usage(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index c3bc1785..13fc60ca 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -22,7 +22,7 @@ class TestAccessKeys: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Codex) -> None: access_key = client.projects.access_keys.create( @@ -31,7 +31,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.create( @@ -46,7 +46,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.create( @@ -59,7 +59,7 @@ def test_raw_response_create(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.create( @@ -74,7 +74,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_create(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -83,7 +83,7 @@ def test_path_params_create(self, client: Codex) -> None: name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve( @@ -92,7 +92,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.retrieve( @@ -105,7 +105,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.retrieve( @@ -120,7 +120,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -135,7 +135,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update(self, client: Codex) -> None: access_key = client.projects.access_keys.update( @@ -145,7 +145,7 @@ def test_method_update(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update_with_all_params(self, client: Codex) -> None: access_key = client.projects.access_keys.update( @@ -157,7 +157,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_update(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.update( @@ -171,7 +171,7 @@ def test_raw_response_update(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_update(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.update( @@ -187,7 +187,7 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_update(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -204,7 +204,7 @@ def test_path_params_update(self, client: Codex) -> None: name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Codex) -> None: access_key = client.projects.access_keys.list( @@ -212,7 +212,7 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.list( @@ -224,7 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.list( @@ -238,7 +238,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -246,7 +246,7 @@ def test_path_params_list(self, client: Codex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_delete(self, client: Codex) -> None: access_key = client.projects.access_keys.delete( @@ -255,7 +255,7 @@ def test_method_delete(self, client: Codex) -> None: ) assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.delete( @@ -268,7 +268,7 @@ def test_raw_response_delete(self, client: Codex) -> None: access_key = response.parse() assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.delete( @@ -283,7 +283,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_delete(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -298,13 +298,13 @@ def test_path_params_delete(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve_project_id(self, client: Codex) -> None: access_key = client.projects.access_keys.retrieve_project_id() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve_project_id(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.retrieve_project_id() @@ -314,7 +314,7 @@ def test_raw_response_retrieve_project_id(self, client: Codex) -> None: access_key = response.parse() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.retrieve_project_id() as response: @@ -326,7 +326,7 @@ def test_streaming_response_retrieve_project_id(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_revoke(self, client: Codex) -> None: access_key = client.projects.access_keys.revoke( @@ -335,7 +335,7 @@ def test_method_revoke(self, client: Codex) -> None: ) assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_revoke(self, client: Codex) -> None: response = client.projects.access_keys.with_raw_response.revoke( @@ -348,7 +348,7 @@ def test_raw_response_revoke(self, client: Codex) -> None: access_key = response.parse() assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_revoke(self, client: Codex) -> None: with client.projects.access_keys.with_streaming_response.revoke( @@ -363,7 +363,7 @@ def test_streaming_response_revoke(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_revoke(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -384,7 +384,7 @@ class TestAsyncAccessKeys: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( @@ -393,7 +393,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.create( @@ -408,7 +408,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.create( @@ -421,7 +421,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.create( @@ -436,7 +436,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_create(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -445,7 +445,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None: name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve( @@ -454,7 +454,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve( @@ -467,7 +467,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve( @@ -482,7 +482,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -497,7 +497,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( @@ -507,7 +507,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.update( @@ -519,7 +519,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.update( @@ -533,7 +533,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeySchema, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.update( @@ -549,7 +549,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_update(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -566,7 +566,7 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.list( @@ -574,7 +574,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.list( @@ -586,7 +586,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert_matches_type(AccessKeyListResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.list( @@ -600,7 +600,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -608,7 +608,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.delete( @@ -617,7 +617,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: ) assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.delete( @@ -630,7 +630,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.delete( @@ -645,7 +645,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_delete(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -660,13 +660,13 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve_project_id(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.retrieve_project_id() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.retrieve_project_id() @@ -676,7 +676,7 @@ async def test_raw_response_retrieve_project_id(self, async_client: AsyncCodex) access_key = await response.parse() assert_matches_type(AccessKeyRetrieveProjectIDResponse, access_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve_project_id(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.retrieve_project_id() as response: @@ -688,7 +688,7 @@ async def test_streaming_response_retrieve_project_id(self, async_client: AsyncC assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_revoke(self, async_client: AsyncCodex) -> None: access_key = await async_client.projects.access_keys.revoke( @@ -697,7 +697,7 @@ async def test_method_revoke(self, async_client: AsyncCodex) -> None: ) assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: response = await async_client.projects.access_keys.with_raw_response.revoke( @@ -710,7 +710,7 @@ async def test_raw_response_revoke(self, async_client: AsyncCodex) -> None: access_key = await response.parse() assert access_key is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None: async with async_client.projects.access_keys.with_streaming_response.revoke( @@ -725,7 +725,7 @@ async def test_streaming_response_revoke(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_revoke(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py index f36de276..7266751d 100644 --- a/tests/api_resources/projects/test_evals.py +++ b/tests/api_resources/projects/test_evals.py @@ -18,7 +18,7 @@ class TestEvals: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Codex) -> None: eval = client.projects.evals.create( @@ -29,7 +29,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: eval = client.projects.evals.create( @@ -50,7 +50,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.evals.with_raw_response.create( @@ -65,7 +65,7 @@ def test_raw_response_create(self, client: Codex) -> None: eval = response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.evals.with_streaming_response.create( @@ -82,7 +82,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_create(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -93,7 +93,7 @@ def test_path_params_create(self, client: Codex) -> None: name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update_overload_1(self, client: Codex) -> None: eval = client.projects.evals.update( @@ -105,7 +105,7 @@ def test_method_update_overload_1(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update_with_all_params_overload_1(self, client: Codex) -> None: eval = client.projects.evals.update( @@ -127,7 +127,7 @@ def test_method_update_with_all_params_overload_1(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_update_overload_1(self, client: Codex) -> None: response = client.projects.evals.with_raw_response.update( @@ -143,7 +143,7 @@ def test_raw_response_update_overload_1(self, client: Codex) -> None: eval = response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_update_overload_1(self, client: Codex) -> None: with client.projects.evals.with_streaming_response.update( @@ -161,7 +161,7 @@ def test_streaming_response_update_overload_1(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_update_overload_1(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -182,7 +182,7 @@ def test_path_params_update_overload_1(self, client: Codex) -> None: name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update_overload_2(self, client: Codex) -> None: eval = client.projects.evals.update( @@ -192,7 +192,7 @@ def test_method_update_overload_2(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update_with_all_params_overload_2(self, client: Codex) -> None: eval = client.projects.evals.update( @@ -208,7 +208,7 @@ def test_method_update_with_all_params_overload_2(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_update_overload_2(self, client: Codex) -> None: response = client.projects.evals.with_raw_response.update( @@ -222,7 +222,7 @@ def test_raw_response_update_overload_2(self, client: Codex) -> None: eval = response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_update_overload_2(self, client: Codex) -> None: with client.projects.evals.with_streaming_response.update( @@ -238,7 +238,7 @@ def test_streaming_response_update_overload_2(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_update_overload_2(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -255,7 +255,7 @@ def test_path_params_update_overload_2(self, client: Codex) -> None: body_eval_key="eval_key", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Codex) -> None: eval = client.projects.evals.list( @@ -263,7 +263,7 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(EvalListResponse, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: eval = client.projects.evals.list( @@ -274,7 +274,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: ) assert_matches_type(EvalListResponse, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.evals.with_raw_response.list( @@ -286,7 +286,7 @@ def test_raw_response_list(self, client: Codex) -> None: eval = response.parse() assert_matches_type(EvalListResponse, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.evals.with_streaming_response.list( @@ -300,7 +300,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -308,7 +308,7 @@ def test_path_params_list(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_delete(self, client: Codex) -> None: eval = client.projects.evals.delete( @@ -317,7 +317,7 @@ def test_method_delete(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.evals.with_raw_response.delete( @@ -330,7 +330,7 @@ def test_raw_response_delete(self, client: Codex) -> None: eval = response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.evals.with_streaming_response.delete( @@ -345,7 +345,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_delete(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -366,7 +366,7 @@ class TestAsyncEvals: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.create( @@ -377,7 +377,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.create( @@ -398,7 +398,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.evals.with_raw_response.create( @@ -413,7 +413,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: eval = await response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.evals.with_streaming_response.create( @@ -430,7 +430,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_create(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -441,7 +441,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None: name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update_overload_1(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.update( @@ -453,7 +453,7 @@ async def test_method_update_overload_1(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update_with_all_params_overload_1(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.update( @@ -475,7 +475,7 @@ async def test_method_update_with_all_params_overload_1(self, async_client: Asyn ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_update_overload_1(self, async_client: AsyncCodex) -> None: response = await async_client.projects.evals.with_raw_response.update( @@ -491,7 +491,7 @@ async def test_raw_response_update_overload_1(self, async_client: AsyncCodex) -> eval = await response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_update_overload_1(self, async_client: AsyncCodex) -> None: async with async_client.projects.evals.with_streaming_response.update( @@ -509,7 +509,7 @@ async def test_streaming_response_update_overload_1(self, async_client: AsyncCod assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_update_overload_1(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -530,7 +530,7 @@ async def test_path_params_update_overload_1(self, async_client: AsyncCodex) -> name="name", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update_overload_2(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.update( @@ -540,7 +540,7 @@ async def test_method_update_overload_2(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update_with_all_params_overload_2(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.update( @@ -556,7 +556,7 @@ async def test_method_update_with_all_params_overload_2(self, async_client: Asyn ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_update_overload_2(self, async_client: AsyncCodex) -> None: response = await async_client.projects.evals.with_raw_response.update( @@ -570,7 +570,7 @@ async def test_raw_response_update_overload_2(self, async_client: AsyncCodex) -> eval = await response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_update_overload_2(self, async_client: AsyncCodex) -> None: async with async_client.projects.evals.with_streaming_response.update( @@ -586,7 +586,7 @@ async def test_streaming_response_update_overload_2(self, async_client: AsyncCod assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_update_overload_2(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -603,7 +603,7 @@ async def test_path_params_update_overload_2(self, async_client: AsyncCodex) -> body_eval_key="eval_key", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.list( @@ -611,7 +611,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(EvalListResponse, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.list( @@ -622,7 +622,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No ) assert_matches_type(EvalListResponse, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.evals.with_raw_response.list( @@ -634,7 +634,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: eval = await response.parse() assert_matches_type(EvalListResponse, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.evals.with_streaming_response.list( @@ -648,7 +648,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -656,7 +656,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: eval = await async_client.projects.evals.delete( @@ -665,7 +665,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.evals.with_raw_response.delete( @@ -678,7 +678,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: eval = await response.parse() assert_matches_type(ProjectReturnSchema, eval, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.evals.with_streaming_response.delete( @@ -693,7 +693,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_delete(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 3e0a96fa..5f7e02cd 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -30,7 +30,7 @@ class TestQueryLogs: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: query_log = client.projects.query_logs.retrieve( @@ -39,7 +39,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.query_logs.with_raw_response.retrieve( @@ -52,7 +52,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: query_log = response.parse() assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.query_logs.with_streaming_response.retrieve( @@ -67,7 +67,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -82,7 +82,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Codex) -> None: query_log = client.projects.query_logs.list( @@ -90,7 +90,7 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: query_log = client.projects.query_logs.list( @@ -112,7 +112,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: ) assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.query_logs.with_raw_response.list( @@ -124,7 +124,7 @@ def test_raw_response_list(self, client: Codex) -> None: query_log = response.parse() assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.query_logs.with_streaming_response.list( @@ -138,7 +138,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -146,7 +146,7 @@ def test_path_params_list(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_by_group(self, client: Codex) -> None: query_log = client.projects.query_logs.list_by_group( @@ -154,7 +154,7 @@ def test_method_list_by_group(self, client: Codex) -> None: ) assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_by_group_with_all_params(self, client: Codex) -> None: query_log = client.projects.query_logs.list_by_group( @@ -178,7 +178,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: ) assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list_by_group(self, client: Codex) -> None: response = client.projects.query_logs.with_raw_response.list_by_group( @@ -190,7 +190,7 @@ def test_raw_response_list_by_group(self, client: Codex) -> None: query_log = response.parse() assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list_by_group(self, client: Codex) -> None: with client.projects.query_logs.with_streaming_response.list_by_group( @@ -204,7 +204,7 @@ def test_streaming_response_list_by_group(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list_by_group(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -212,7 +212,7 @@ def test_path_params_list_by_group(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_groups(self, client: Codex) -> None: query_log = client.projects.query_logs.list_groups( @@ -220,7 +220,7 @@ def test_method_list_groups(self, client: Codex) -> None: ) assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_groups_with_all_params(self, client: Codex) -> None: query_log = client.projects.query_logs.list_groups( @@ -243,7 +243,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: ) assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list_groups(self, client: Codex) -> None: response = client.projects.query_logs.with_raw_response.list_groups( @@ -255,7 +255,7 @@ def test_raw_response_list_groups(self, client: Codex) -> None: query_log = response.parse() assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list_groups(self, client: Codex) -> None: with client.projects.query_logs.with_streaming_response.list_groups( @@ -269,7 +269,7 @@ def test_streaming_response_list_groups(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list_groups(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -277,7 +277,7 @@ def test_path_params_list_groups(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_start_remediation(self, client: Codex) -> None: query_log = client.projects.query_logs.start_remediation( @@ -286,7 +286,7 @@ def test_method_start_remediation(self, client: Codex) -> None: ) assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_start_remediation(self, client: Codex) -> None: response = client.projects.query_logs.with_raw_response.start_remediation( @@ -299,7 +299,7 @@ def test_raw_response_start_remediation(self, client: Codex) -> None: query_log = response.parse() assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_start_remediation(self, client: Codex) -> None: with client.projects.query_logs.with_streaming_response.start_remediation( @@ -314,7 +314,7 @@ def test_streaming_response_start_remediation(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_start_remediation(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -335,7 +335,7 @@ class TestAsyncQueryLogs: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.retrieve( @@ -344,7 +344,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.query_logs.with_raw_response.retrieve( @@ -357,7 +357,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: query_log = await response.parse() assert_matches_type(QueryLogRetrieveResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.query_logs.with_streaming_response.retrieve( @@ -372,7 +372,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -387,7 +387,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list( @@ -395,7 +395,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list( @@ -417,7 +417,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No ) assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.query_logs.with_raw_response.list( @@ -429,7 +429,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: query_log = await response.parse() assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.query_logs.with_streaming_response.list( @@ -443,7 +443,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -451,7 +451,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_by_group(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list_by_group( @@ -459,7 +459,7 @@ async def test_method_list_by_group(self, async_client: AsyncCodex) -> None: ) assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_by_group_with_all_params(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list_by_group( @@ -483,7 +483,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod ) assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> None: response = await async_client.projects.query_logs.with_raw_response.list_by_group( @@ -495,7 +495,7 @@ async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> Non query_log = await response.parse() assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list_by_group(self, async_client: AsyncCodex) -> None: async with async_client.projects.query_logs.with_streaming_response.list_by_group( @@ -509,7 +509,7 @@ async def test_streaming_response_list_by_group(self, async_client: AsyncCodex) assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list_by_group(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -517,7 +517,7 @@ async def test_path_params_list_by_group(self, async_client: AsyncCodex) -> None project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_groups(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list_groups( @@ -525,7 +525,7 @@ async def test_method_list_groups(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.list_groups( @@ -548,7 +548,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex ) assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None: response = await async_client.projects.query_logs.with_raw_response.list_groups( @@ -560,7 +560,7 @@ async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None: query_log = await response.parse() assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list_groups(self, async_client: AsyncCodex) -> None: async with async_client.projects.query_logs.with_streaming_response.list_groups( @@ -574,7 +574,7 @@ async def test_streaming_response_list_groups(self, async_client: AsyncCodex) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list_groups(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -582,7 +582,7 @@ async def test_path_params_list_groups(self, async_client: AsyncCodex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_start_remediation(self, async_client: AsyncCodex) -> None: query_log = await async_client.projects.query_logs.start_remediation( @@ -591,7 +591,7 @@ async def test_method_start_remediation(self, async_client: AsyncCodex) -> None: ) assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_start_remediation(self, async_client: AsyncCodex) -> None: response = await async_client.projects.query_logs.with_raw_response.start_remediation( @@ -604,7 +604,7 @@ async def test_raw_response_start_remediation(self, async_client: AsyncCodex) -> query_log = await response.parse() assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_start_remediation(self, async_client: AsyncCodex) -> None: async with async_client.projects.query_logs.with_streaming_response.start_remediation( @@ -619,7 +619,7 @@ async def test_streaming_response_start_remediation(self, async_client: AsyncCod assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_start_remediation(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py index 5866dbe7..d547ac85 100644 --- a/tests/api_resources/projects/test_remediations.py +++ b/tests/api_resources/projects/test_remediations.py @@ -30,7 +30,7 @@ class TestRemediations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Codex) -> None: remediation = client.projects.remediations.create( @@ -39,7 +39,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: remediation = client.projects.remediations.create( @@ -50,7 +50,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: ) assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.create( @@ -63,7 +63,7 @@ def test_raw_response_create(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.create( @@ -78,7 +78,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_create(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -87,7 +87,7 @@ def test_path_params_create(self, client: Codex) -> None: question="x", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: remediation = client.projects.remediations.retrieve( @@ -96,7 +96,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.retrieve( @@ -109,7 +109,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.retrieve( @@ -124,7 +124,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -139,7 +139,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Codex) -> None: remediation = client.projects.remediations.list( @@ -147,7 +147,7 @@ def test_method_list(self, client: Codex) -> None: ) assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: remediation = client.projects.remediations.list( @@ -165,7 +165,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: ) assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.list( @@ -177,7 +177,7 @@ def test_raw_response_list(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.list( @@ -191,7 +191,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -199,7 +199,7 @@ def test_path_params_list(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_delete(self, client: Codex) -> None: remediation = client.projects.remediations.delete( @@ -208,7 +208,7 @@ def test_method_delete(self, client: Codex) -> None: ) assert remediation is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.delete( @@ -221,7 +221,7 @@ def test_raw_response_delete(self, client: Codex) -> None: remediation = response.parse() assert remediation is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.delete( @@ -236,7 +236,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_delete(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -251,7 +251,7 @@ def test_path_params_delete(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_edit_answer(self, client: Codex) -> None: remediation = client.projects.remediations.edit_answer( @@ -261,7 +261,7 @@ def test_method_edit_answer(self, client: Codex) -> None: ) assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_edit_answer(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.edit_answer( @@ -275,7 +275,7 @@ def test_raw_response_edit_answer(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_edit_answer(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.edit_answer( @@ -291,7 +291,7 @@ def test_streaming_response_edit_answer(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_edit_answer(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -308,7 +308,7 @@ def test_path_params_edit_answer(self, client: Codex) -> None: answer="answer", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_edit_draft_answer(self, client: Codex) -> None: remediation = client.projects.remediations.edit_draft_answer( @@ -318,7 +318,7 @@ def test_method_edit_draft_answer(self, client: Codex) -> None: ) assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_edit_draft_answer(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.edit_draft_answer( @@ -332,7 +332,7 @@ def test_raw_response_edit_draft_answer(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_edit_draft_answer(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.edit_draft_answer( @@ -348,7 +348,7 @@ def test_streaming_response_edit_draft_answer(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_edit_draft_answer(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -365,7 +365,7 @@ def test_path_params_edit_draft_answer(self, client: Codex) -> None: draft_answer="draft_answer", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_get_resolved_logs_count(self, client: Codex) -> None: remediation = client.projects.remediations.get_resolved_logs_count( @@ -374,7 +374,7 @@ def test_method_get_resolved_logs_count(self, client: Codex) -> None: ) assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.get_resolved_logs_count( @@ -387,7 +387,7 @@ def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_get_resolved_logs_count(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.get_resolved_logs_count( @@ -402,7 +402,7 @@ def test_streaming_response_get_resolved_logs_count(self, client: Codex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_get_resolved_logs_count(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -417,7 +417,7 @@ def test_path_params_get_resolved_logs_count(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_resolved_logs(self, client: Codex) -> None: remediation = client.projects.remediations.list_resolved_logs( @@ -426,7 +426,7 @@ def test_method_list_resolved_logs(self, client: Codex) -> None: ) assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list_resolved_logs(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.list_resolved_logs( @@ -439,7 +439,7 @@ def test_raw_response_list_resolved_logs(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list_resolved_logs(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.list_resolved_logs( @@ -454,7 +454,7 @@ def test_streaming_response_list_resolved_logs(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list_resolved_logs(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -469,7 +469,7 @@ def test_path_params_list_resolved_logs(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_pause(self, client: Codex) -> None: remediation = client.projects.remediations.pause( @@ -478,7 +478,7 @@ def test_method_pause(self, client: Codex) -> None: ) assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_pause(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.pause( @@ -491,7 +491,7 @@ def test_raw_response_pause(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_pause(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.pause( @@ -506,7 +506,7 @@ def test_streaming_response_pause(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_pause(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -521,7 +521,7 @@ def test_path_params_pause(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_publish(self, client: Codex) -> None: remediation = client.projects.remediations.publish( @@ -530,7 +530,7 @@ def test_method_publish(self, client: Codex) -> None: ) assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_publish(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.publish( @@ -543,7 +543,7 @@ def test_raw_response_publish(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_publish(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.publish( @@ -558,7 +558,7 @@ def test_streaming_response_publish(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_publish(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -573,7 +573,7 @@ def test_path_params_publish(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_unpause(self, client: Codex) -> None: remediation = client.projects.remediations.unpause( @@ -582,7 +582,7 @@ def test_method_unpause(self, client: Codex) -> None: ) assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_unpause(self, client: Codex) -> None: response = client.projects.remediations.with_raw_response.unpause( @@ -595,7 +595,7 @@ def test_raw_response_unpause(self, client: Codex) -> None: remediation = response.parse() assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_unpause(self, client: Codex) -> None: with client.projects.remediations.with_streaming_response.unpause( @@ -610,7 +610,7 @@ def test_streaming_response_unpause(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_unpause(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -631,7 +631,7 @@ class TestAsyncRemediations: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.create( @@ -640,7 +640,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.create( @@ -651,7 +651,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.create( @@ -664,7 +664,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.create( @@ -679,7 +679,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_create(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -688,7 +688,7 @@ async def test_path_params_create(self, async_client: AsyncCodex) -> None: question="x", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.retrieve( @@ -697,7 +697,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.retrieve( @@ -710,7 +710,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.retrieve( @@ -725,7 +725,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -740,7 +740,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.list( @@ -748,7 +748,7 @@ async def test_method_list(self, async_client: AsyncCodex) -> None: ) assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.list( @@ -766,7 +766,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No ) assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.list( @@ -778,7 +778,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.list( @@ -792,7 +792,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -800,7 +800,7 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.delete( @@ -809,7 +809,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: ) assert remediation is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.delete( @@ -822,7 +822,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert remediation is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.delete( @@ -837,7 +837,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_delete(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -852,7 +852,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_edit_answer(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.edit_answer( @@ -862,7 +862,7 @@ async def test_method_edit_answer(self, async_client: AsyncCodex) -> None: ) assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.edit_answer( @@ -876,7 +876,7 @@ async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.edit_answer( @@ -892,7 +892,7 @@ async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -909,7 +909,7 @@ async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None: answer="answer", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.edit_draft_answer( @@ -919,7 +919,7 @@ async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None: ) assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.edit_draft_answer( @@ -933,7 +933,7 @@ async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> remediation = await response.parse() assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.edit_draft_answer( @@ -949,7 +949,7 @@ async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCod assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -966,7 +966,7 @@ async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) -> draft_answer="draft_answer", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.get_resolved_logs_count( @@ -975,7 +975,7 @@ async def test_method_get_resolved_logs_count(self, async_client: AsyncCodex) -> ) assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( @@ -988,7 +988,7 @@ async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCod remediation = await response.parse() assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.get_resolved_logs_count( @@ -1003,7 +1003,7 @@ async def test_streaming_response_get_resolved_logs_count(self, async_client: As assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1018,7 +1018,7 @@ async def test_path_params_get_resolved_logs_count(self, async_client: AsyncCode project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_resolved_logs(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.list_resolved_logs( @@ -1027,7 +1027,7 @@ async def test_method_list_resolved_logs(self, async_client: AsyncCodex) -> None ) assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.list_resolved_logs( @@ -1040,7 +1040,7 @@ async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) - remediation = await response.parse() assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list_resolved_logs(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.list_resolved_logs( @@ -1055,7 +1055,7 @@ async def test_streaming_response_list_resolved_logs(self, async_client: AsyncCo assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list_resolved_logs(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1070,7 +1070,7 @@ async def test_path_params_list_resolved_logs(self, async_client: AsyncCodex) -> project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_pause(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.pause( @@ -1079,7 +1079,7 @@ async def test_method_pause(self, async_client: AsyncCodex) -> None: ) assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_pause(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.pause( @@ -1092,7 +1092,7 @@ async def test_raw_response_pause(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.pause( @@ -1107,7 +1107,7 @@ async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_pause(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1122,7 +1122,7 @@ async def test_path_params_pause(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_publish(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.publish( @@ -1131,7 +1131,7 @@ async def test_method_publish(self, async_client: AsyncCodex) -> None: ) assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_publish(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.publish( @@ -1144,7 +1144,7 @@ async def test_raw_response_publish(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_publish(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.publish( @@ -1159,7 +1159,7 @@ async def test_streaming_response_publish(self, async_client: AsyncCodex) -> Non assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_publish(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1174,7 +1174,7 @@ async def test_path_params_publish(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_unpause(self, async_client: AsyncCodex) -> None: remediation = await async_client.projects.remediations.unpause( @@ -1183,7 +1183,7 @@ async def test_method_unpause(self, async_client: AsyncCodex) -> None: ) assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None: response = await async_client.projects.remediations.with_raw_response.unpause( @@ -1196,7 +1196,7 @@ async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None: remediation = await response.parse() assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> None: async with async_client.projects.remediations.with_streaming_response.unpause( @@ -1211,7 +1211,7 @@ async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> Non assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_unpause(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py index 92db3a81..0baadf50 100644 --- a/tests/api_resources/test_health.py +++ b/tests/api_resources/test_health.py @@ -17,13 +17,13 @@ class TestHealth: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_check(self, client: Codex) -> None: health = client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_check(self, client: Codex) -> None: response = client.health.with_raw_response.check() @@ -33,7 +33,7 @@ def test_raw_response_check(self, client: Codex) -> None: health = response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_check(self, client: Codex) -> None: with client.health.with_streaming_response.check() as response: @@ -45,13 +45,13 @@ def test_streaming_response_check(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_db(self, client: Codex) -> None: health = client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_db(self, client: Codex) -> None: response = client.health.with_raw_response.db() @@ -61,7 +61,7 @@ def test_raw_response_db(self, client: Codex) -> None: health = response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_db(self, client: Codex) -> None: with client.health.with_streaming_response.db() as response: @@ -79,13 +79,13 @@ class TestAsyncHealth: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_check(self, async_client: AsyncCodex) -> None: health = await async_client.health.check() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_check(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.check() @@ -95,7 +95,7 @@ async def test_raw_response_check(self, async_client: AsyncCodex) -> None: health = await response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.check() as response: @@ -107,13 +107,13 @@ async def test_streaming_response_check(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_db(self, async_client: AsyncCodex) -> None: health = await async_client.health.db() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_db(self, async_client: AsyncCodex) -> None: response = await async_client.health.with_raw_response.db() @@ -123,7 +123,7 @@ async def test_raw_response_db(self, async_client: AsyncCodex) -> None: health = await response.parse() assert_matches_type(HealthCheckResponse, health, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_db(self, async_client: AsyncCodex) -> None: async with async_client.health.with_streaming_response.db() as response: diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index eecdf3f7..9245bd89 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -21,7 +21,7 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: organization = client.organizations.retrieve( @@ -29,7 +29,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.organizations.with_raw_response.retrieve( @@ -41,7 +41,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: organization = response.parse() assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.organizations.with_streaming_response.retrieve( @@ -55,7 +55,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -63,7 +63,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_members(self, client: Codex) -> None: organization = client.organizations.list_members( @@ -71,7 +71,7 @@ def test_method_list_members(self, client: Codex) -> None: ) assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list_members(self, client: Codex) -> None: response = client.organizations.with_raw_response.list_members( @@ -83,7 +83,7 @@ def test_raw_response_list_members(self, client: Codex) -> None: organization = response.parse() assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list_members(self, client: Codex) -> None: with client.organizations.with_streaming_response.list_members( @@ -97,7 +97,7 @@ def test_streaming_response_list_members(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list_members(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -105,7 +105,7 @@ def test_path_params_list_members(self, client: Codex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve_permissions(self, client: Codex) -> None: organization = client.organizations.retrieve_permissions( @@ -113,7 +113,7 @@ def test_method_retrieve_permissions(self, client: Codex) -> None: ) assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve_permissions(self, client: Codex) -> None: response = client.organizations.with_raw_response.retrieve_permissions( @@ -125,7 +125,7 @@ def test_raw_response_retrieve_permissions(self, client: Codex) -> None: organization = response.parse() assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve_permissions(self, client: Codex) -> None: with client.organizations.with_streaming_response.retrieve_permissions( @@ -139,7 +139,7 @@ def test_streaming_response_retrieve_permissions(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve_permissions(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -153,7 +153,7 @@ class TestAsyncOrganizations: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: organization = await async_client.organizations.retrieve( @@ -161,7 +161,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.with_raw_response.retrieve( @@ -173,7 +173,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: organization = await response.parse() assert_matches_type(OrganizationSchemaPublic, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.organizations.with_streaming_response.retrieve( @@ -187,7 +187,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -195,7 +195,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_members(self, async_client: AsyncCodex) -> None: organization = await async_client.organizations.list_members( @@ -203,7 +203,7 @@ async def test_method_list_members(self, async_client: AsyncCodex) -> None: ) assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list_members(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.with_raw_response.list_members( @@ -215,7 +215,7 @@ async def test_raw_response_list_members(self, async_client: AsyncCodex) -> None organization = await response.parse() assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list_members(self, async_client: AsyncCodex) -> None: async with async_client.organizations.with_streaming_response.list_members( @@ -229,7 +229,7 @@ async def test_streaming_response_list_members(self, async_client: AsyncCodex) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list_members(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): @@ -237,7 +237,7 @@ async def test_path_params_list_members(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve_permissions(self, async_client: AsyncCodex) -> None: organization = await async_client.organizations.retrieve_permissions( @@ -245,7 +245,7 @@ async def test_method_retrieve_permissions(self, async_client: AsyncCodex) -> No ) assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve_permissions(self, async_client: AsyncCodex) -> None: response = await async_client.organizations.with_raw_response.retrieve_permissions( @@ -257,7 +257,7 @@ async def test_raw_response_retrieve_permissions(self, async_client: AsyncCodex) organization = await response.parse() assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve_permissions(self, async_client: AsyncCodex) -> None: async with async_client.organizations.with_streaming_response.retrieve_permissions( @@ -271,7 +271,7 @@ async def test_streaming_response_retrieve_permissions(self, async_client: Async assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve_permissions(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 7884db0f..2159a1f0 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -24,7 +24,7 @@ class TestProjects: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Codex) -> None: project = client.projects.create( @@ -34,7 +34,7 @@ def test_method_create(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: project = client.projects.create( @@ -128,7 +128,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Codex) -> None: response = client.projects.with_raw_response.create( @@ -142,7 +142,7 @@ def test_raw_response_create(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Codex) -> None: with client.projects.with_streaming_response.create( @@ -158,7 +158,7 @@ def test_streaming_response_create(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: project = client.projects.retrieve( @@ -166,7 +166,7 @@ def test_method_retrieve(self, client: Codex) -> None: ) assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.projects.with_raw_response.retrieve( @@ -178,7 +178,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.projects.with_streaming_response.retrieve( @@ -192,7 +192,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -200,7 +200,7 @@ def test_path_params_retrieve(self, client: Codex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update(self, client: Codex) -> None: project = client.projects.update( @@ -208,7 +208,7 @@ def test_method_update(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_update_with_all_params(self, client: Codex) -> None: project = client.projects.update( @@ -302,7 +302,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_update(self, client: Codex) -> None: response = client.projects.with_raw_response.update( @@ -314,7 +314,7 @@ def test_raw_response_update(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_update(self, client: Codex) -> None: with client.projects.with_streaming_response.update( @@ -328,7 +328,7 @@ def test_streaming_response_update(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_update(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -336,13 +336,13 @@ def test_path_params_update(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Codex) -> None: project = client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( @@ -356,7 +356,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectListResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.projects.with_raw_response.list() @@ -366,7 +366,7 @@ def test_raw_response_list(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectListResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.projects.with_streaming_response.list() as response: @@ -378,7 +378,7 @@ def test_streaming_response_list(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_delete(self, client: Codex) -> None: project = client.projects.delete( @@ -386,7 +386,7 @@ def test_method_delete(self, client: Codex) -> None: ) assert project is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_delete(self, client: Codex) -> None: response = client.projects.with_raw_response.delete( @@ -398,7 +398,7 @@ def test_raw_response_delete(self, client: Codex) -> None: project = response.parse() assert project is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_delete(self, client: Codex) -> None: with client.projects.with_streaming_response.delete( @@ -412,7 +412,7 @@ def test_streaming_response_delete(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_delete(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -420,7 +420,7 @@ def test_path_params_delete(self, client: Codex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_export(self, client: Codex) -> None: project = client.projects.export( @@ -428,7 +428,7 @@ def test_method_export(self, client: Codex) -> None: ) assert_matches_type(object, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_export(self, client: Codex) -> None: response = client.projects.with_raw_response.export( @@ -440,7 +440,7 @@ def test_raw_response_export(self, client: Codex) -> None: project = response.parse() assert_matches_type(object, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_export(self, client: Codex) -> None: with client.projects.with_streaming_response.export( @@ -454,7 +454,7 @@ def test_streaming_response_export(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_export(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -462,7 +462,7 @@ def test_path_params_export(self, client: Codex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_invite_sme(self, client: Codex) -> None: project = client.projects.invite_sme( @@ -473,7 +473,7 @@ def test_method_invite_sme(self, client: Codex) -> None: ) assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_invite_sme(self, client: Codex) -> None: response = client.projects.with_raw_response.invite_sme( @@ -488,7 +488,7 @@ def test_raw_response_invite_sme(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_invite_sme(self, client: Codex) -> None: with client.projects.with_streaming_response.invite_sme( @@ -505,7 +505,7 @@ def test_streaming_response_invite_sme(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_invite_sme(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -516,7 +516,7 @@ def test_path_params_invite_sme(self, client: Codex) -> None: url_query_string="url_query_string", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve_analytics(self, client: Codex) -> None: project = client.projects.retrieve_analytics( @@ -524,7 +524,7 @@ def test_method_retrieve_analytics(self, client: Codex) -> None: ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None: project = client.projects.retrieve_analytics( @@ -534,7 +534,7 @@ def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve_analytics(self, client: Codex) -> None: response = client.projects.with_raw_response.retrieve_analytics( @@ -546,7 +546,7 @@ def test_raw_response_retrieve_analytics(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve_analytics(self, client: Codex) -> None: with client.projects.with_streaming_response.retrieve_analytics( @@ -560,7 +560,7 @@ def test_streaming_response_retrieve_analytics(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve_analytics(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -568,7 +568,7 @@ def test_path_params_retrieve_analytics(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_validate(self, client: Codex) -> None: project = client.projects.validate( @@ -579,7 +579,7 @@ def test_method_validate(self, client: Codex) -> None: ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_validate_with_all_params(self, client: Codex) -> None: project = client.projects.validate( @@ -649,7 +649,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_validate(self, client: Codex) -> None: response = client.projects.with_raw_response.validate( @@ -664,7 +664,7 @@ def test_raw_response_validate(self, client: Codex) -> None: project = response.parse() assert_matches_type(ProjectValidateResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_validate(self, client: Codex) -> None: with client.projects.with_streaming_response.validate( @@ -681,7 +681,7 @@ def test_streaming_response_validate(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_validate(self, client: Codex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -698,7 +698,7 @@ class TestAsyncProjects: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( @@ -708,7 +708,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( @@ -802,7 +802,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.create( @@ -816,7 +816,7 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.create( @@ -832,7 +832,7 @@ async def test_streaming_response_create(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: project = await async_client.projects.retrieve( @@ -840,7 +840,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.retrieve( @@ -852,7 +852,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.retrieve( @@ -866,7 +866,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -874,7 +874,7 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( @@ -882,7 +882,7 @@ async def test_method_update(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.update( @@ -976,7 +976,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_update(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.update( @@ -988,7 +988,7 @@ async def test_raw_response_update(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectReturnSchema, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_update(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.update( @@ -1002,7 +1002,7 @@ async def test_streaming_response_update(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_update(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1010,13 +1010,13 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( @@ -1030,7 +1030,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No ) assert_matches_type(ProjectListResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.list() @@ -1040,7 +1040,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectListResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.list() as response: @@ -1052,7 +1052,7 @@ async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: project = await async_client.projects.delete( @@ -1060,7 +1060,7 @@ async def test_method_delete(self, async_client: AsyncCodex) -> None: ) assert project is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.delete( @@ -1072,7 +1072,7 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: project = await response.parse() assert project is None - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.delete( @@ -1086,7 +1086,7 @@ async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_delete(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1094,7 +1094,7 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_export(self, async_client: AsyncCodex) -> None: project = await async_client.projects.export( @@ -1102,7 +1102,7 @@ async def test_method_export(self, async_client: AsyncCodex) -> None: ) assert_matches_type(object, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_export(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.export( @@ -1114,7 +1114,7 @@ async def test_raw_response_export(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(object, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_export(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.export( @@ -1128,7 +1128,7 @@ async def test_streaming_response_export(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_export(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1136,7 +1136,7 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None: "", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_invite_sme(self, async_client: AsyncCodex) -> None: project = await async_client.projects.invite_sme( @@ -1147,7 +1147,7 @@ async def test_method_invite_sme(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_invite_sme(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.invite_sme( @@ -1162,7 +1162,7 @@ async def test_raw_response_invite_sme(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectInviteSmeResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_invite_sme(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.invite_sme( @@ -1179,7 +1179,7 @@ async def test_streaming_response_invite_sme(self, async_client: AsyncCodex) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_invite_sme(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1190,7 +1190,7 @@ async def test_path_params_invite_sme(self, async_client: AsyncCodex) -> None: url_query_string="url_query_string", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None: project = await async_client.projects.retrieve_analytics( @@ -1198,7 +1198,7 @@ async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve_analytics_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.retrieve_analytics( @@ -1208,7 +1208,7 @@ async def test_method_retrieve_analytics_with_all_params(self, async_client: Asy ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve_analytics(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.retrieve_analytics( @@ -1220,7 +1220,7 @@ async def test_raw_response_retrieve_analytics(self, async_client: AsyncCodex) - project = await response.parse() assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve_analytics(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.retrieve_analytics( @@ -1234,7 +1234,7 @@ async def test_streaming_response_retrieve_analytics(self, async_client: AsyncCo assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): @@ -1242,7 +1242,7 @@ async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) -> project_id="", ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_validate(self, async_client: AsyncCodex) -> None: project = await async_client.projects.validate( @@ -1253,7 +1253,7 @@ async def test_method_validate(self, async_client: AsyncCodex) -> None: ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_validate_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.validate( @@ -1323,7 +1323,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - ) assert_matches_type(ProjectValidateResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_validate(self, async_client: AsyncCodex) -> None: response = await async_client.projects.with_raw_response.validate( @@ -1338,7 +1338,7 @@ async def test_raw_response_validate(self, async_client: AsyncCodex) -> None: project = await response.parse() assert_matches_type(ProjectValidateResponse, project, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_validate(self, async_client: AsyncCodex) -> None: async with async_client.projects.with_streaming_response.validate( @@ -1355,7 +1355,7 @@ async def test_streaming_response_validate(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_validate(self, async_client: AsyncCodex) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index da0a9ad1..fd78e60c 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -17,7 +17,7 @@ class TestTlm: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_prompt(self, client: Codex) -> None: tlm = client.tlm.prompt( @@ -25,7 +25,7 @@ def test_method_prompt(self, client: Codex) -> None: ) assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_prompt_with_all_params(self, client: Codex) -> None: tlm = client.tlm.prompt( @@ -48,7 +48,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: ) assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_prompt(self, client: Codex) -> None: response = client.tlm.with_raw_response.prompt( @@ -60,7 +60,7 @@ def test_raw_response_prompt(self, client: Codex) -> None: tlm = response.parse() assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_prompt(self, client: Codex) -> None: with client.tlm.with_streaming_response.prompt( @@ -74,7 +74,7 @@ def test_streaming_response_prompt(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_score(self, client: Codex) -> None: tlm = client.tlm.score( @@ -83,7 +83,7 @@ def test_method_score(self, client: Codex) -> None: ) assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_score_with_all_params(self, client: Codex) -> None: tlm = client.tlm.score( @@ -107,7 +107,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None: ) assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_score(self, client: Codex) -> None: response = client.tlm.with_raw_response.score( @@ -120,7 +120,7 @@ def test_raw_response_score(self, client: Codex) -> None: tlm = response.parse() assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_score(self, client: Codex) -> None: with client.tlm.with_streaming_response.score( @@ -141,7 +141,7 @@ class TestAsyncTlm: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_prompt(self, async_client: AsyncCodex) -> None: tlm = await async_client.tlm.prompt( @@ -149,7 +149,7 @@ async def test_method_prompt(self, async_client: AsyncCodex) -> None: ) assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> None: tlm = await async_client.tlm.prompt( @@ -172,7 +172,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> ) assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_prompt(self, async_client: AsyncCodex) -> None: response = await async_client.tlm.with_raw_response.prompt( @@ -184,7 +184,7 @@ async def test_raw_response_prompt(self, async_client: AsyncCodex) -> None: tlm = await response.parse() assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_prompt(self, async_client: AsyncCodex) -> None: async with async_client.tlm.with_streaming_response.prompt( @@ -198,7 +198,7 @@ async def test_streaming_response_prompt(self, async_client: AsyncCodex) -> None assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_score(self, async_client: AsyncCodex) -> None: tlm = await async_client.tlm.score( @@ -207,7 +207,7 @@ async def test_method_score(self, async_client: AsyncCodex) -> None: ) assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> None: tlm = await async_client.tlm.score( @@ -231,7 +231,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N ) assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_score(self, async_client: AsyncCodex) -> None: response = await async_client.tlm.with_raw_response.score( @@ -244,7 +244,7 @@ async def test_raw_response_score(self, async_client: AsyncCodex) -> None: tlm = await response.parse() assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_score(self, async_client: AsyncCodex) -> None: async with async_client.tlm.with_streaming_response.score( diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index 661ee559..4526254d 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -18,7 +18,7 @@ class TestUsers: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_activate_account(self, client: Codex) -> None: user = client.users.activate_account( @@ -27,7 +27,7 @@ def test_method_activate_account(self, client: Codex) -> None: ) assert_matches_type(UserSchemaPublic, user, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_activate_account_with_all_params(self, client: Codex) -> None: user = client.users.activate_account( @@ -41,7 +41,7 @@ def test_method_activate_account_with_all_params(self, client: Codex) -> None: ) assert_matches_type(UserSchemaPublic, user, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_activate_account(self, client: Codex) -> None: response = client.users.with_raw_response.activate_account( @@ -54,7 +54,7 @@ def test_raw_response_activate_account(self, client: Codex) -> None: user = response.parse() assert_matches_type(UserSchemaPublic, user, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_activate_account(self, client: Codex) -> None: with client.users.with_streaming_response.activate_account( @@ -75,7 +75,7 @@ class TestAsyncUsers: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_activate_account(self, async_client: AsyncCodex) -> None: user = await async_client.users.activate_account( @@ -84,7 +84,7 @@ async def test_method_activate_account(self, async_client: AsyncCodex) -> None: ) assert_matches_type(UserSchemaPublic, user, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_activate_account_with_all_params(self, async_client: AsyncCodex) -> None: user = await async_client.users.activate_account( @@ -98,7 +98,7 @@ async def test_method_activate_account_with_all_params(self, async_client: Async ) assert_matches_type(UserSchemaPublic, user, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_activate_account(self, async_client: AsyncCodex) -> None: response = await async_client.users.with_raw_response.activate_account( @@ -111,7 +111,7 @@ async def test_raw_response_activate_account(self, async_client: AsyncCodex) -> user = await response.parse() assert_matches_type(UserSchemaPublic, user, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_activate_account(self, async_client: AsyncCodex) -> None: async with async_client.users.with_streaming_response.activate_account( diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index f0a7ccf7..65cff9ad 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -17,13 +17,13 @@ class TestAPIKey: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: api_key = client.users.myself.api_key.retrieve() assert_matches_type(UserSchemaPublic, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.users.myself.api_key.with_raw_response.retrieve() @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: api_key = response.parse() assert_matches_type(UserSchemaPublic, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.users.myself.api_key.with_streaming_response.retrieve() as response: @@ -45,13 +45,13 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_refresh(self, client: Codex) -> None: api_key = client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_refresh(self, client: Codex) -> None: response = client.users.myself.api_key.with_raw_response.refresh() @@ -61,7 +61,7 @@ def test_raw_response_refresh(self, client: Codex) -> None: api_key = response.parse() assert_matches_type(UserSchema, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_refresh(self, client: Codex) -> None: with client.users.myself.api_key.with_streaming_response.refresh() as response: @@ -79,13 +79,13 @@ class TestAsyncAPIKey: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: api_key = await async_client.users.myself.api_key.retrieve() assert_matches_type(UserSchemaPublic, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.api_key.with_raw_response.retrieve() @@ -95,7 +95,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: api_key = await response.parse() assert_matches_type(UserSchemaPublic, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.api_key.with_streaming_response.retrieve() as response: @@ -107,13 +107,13 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_refresh(self, async_client: AsyncCodex) -> None: api_key = await async_client.users.myself.api_key.refresh() assert_matches_type(UserSchema, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.api_key.with_raw_response.refresh() @@ -123,7 +123,7 @@ async def test_raw_response_refresh(self, async_client: AsyncCodex) -> None: api_key = await response.parse() assert_matches_type(UserSchema, api_key, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_refresh(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.api_key.with_streaming_response.refresh() as response: diff --git a/tests/api_resources/users/myself/test_organizations.py b/tests/api_resources/users/myself/test_organizations.py index fd377ea0..d30c0b60 100644 --- a/tests/api_resources/users/myself/test_organizations.py +++ b/tests/api_resources/users/myself/test_organizations.py @@ -17,13 +17,13 @@ class TestOrganizations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Codex) -> None: organization = client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Codex) -> None: response = client.users.myself.organizations.with_raw_response.list() @@ -33,7 +33,7 @@ def test_raw_response_list(self, client: Codex) -> None: organization = response.parse() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Codex) -> None: with client.users.myself.organizations.with_streaming_response.list() as response: @@ -51,13 +51,13 @@ class TestAsyncOrganizations: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: organization = await async_client.users.myself.organizations.list() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.organizations.with_raw_response.list() @@ -67,7 +67,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: organization = await response.parse() assert_matches_type(UserOrganizationsSchema, organization, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.organizations.with_streaming_response.list() as response: diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index 1c56b0be..f86d54cb 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -17,13 +17,13 @@ class TestMyself: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: myself = client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: response = client.users.myself.with_raw_response.retrieve() @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: myself = response.parse() assert_matches_type(UserSchemaPublic, myself, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: with client.users.myself.with_streaming_response.retrieve() as response: @@ -51,13 +51,13 @@ class TestAsyncMyself: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: myself = await async_client.users.myself.retrieve() assert_matches_type(UserSchemaPublic, myself, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: response = await async_client.users.myself.with_raw_response.retrieve() @@ -67,7 +67,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: myself = await response.parse() assert_matches_type(UserSchemaPublic, myself, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: async with async_client.users.myself.with_streaming_response.retrieve() as response: diff --git a/tests/api_resources/users/test_verification.py b/tests/api_resources/users/test_verification.py index fbf6b667..3fca3582 100644 --- a/tests/api_resources/users/test_verification.py +++ b/tests/api_resources/users/test_verification.py @@ -17,13 +17,13 @@ class TestVerification: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_resend(self, client: Codex) -> None: verification = client.users.verification.resend() assert_matches_type(VerificationResendResponse, verification, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_resend(self, client: Codex) -> None: response = client.users.verification.with_raw_response.resend() @@ -33,7 +33,7 @@ def test_raw_response_resend(self, client: Codex) -> None: verification = response.parse() assert_matches_type(VerificationResendResponse, verification, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_resend(self, client: Codex) -> None: with client.users.verification.with_streaming_response.resend() as response: @@ -51,13 +51,13 @@ class TestAsyncVerification: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_resend(self, async_client: AsyncCodex) -> None: verification = await async_client.users.verification.resend() assert_matches_type(VerificationResendResponse, verification, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_resend(self, async_client: AsyncCodex) -> None: response = await async_client.users.verification.with_raw_response.resend() @@ -67,7 +67,7 @@ async def test_raw_response_resend(self, async_client: AsyncCodex) -> None: verification = await response.parse() assert_matches_type(VerificationResendResponse, verification, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_resend(self, async_client: AsyncCodex) -> None: async with async_client.users.verification.with_streaming_response.resend() as response: From 95b308a34464c1830399711e897029bf6ce59ad9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:17:49 +0000 Subject: [PATCH 225/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 172 ++++++----- src/codex/resources/tlm.py | 344 +++++++++++---------- src/codex/types/project_validate_params.py | 88 +++--- src/codex/types/tlm_prompt_params.py | 88 +++--- src/codex/types/tlm_score_params.py | 88 +++--- tests/api_resources/test_projects.py | 2 + tests/api_resources/test_tlm.py | 4 + 8 files changed, 427 insertions(+), 361 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3aa68e69..5e0a177c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: d01153406f196329a5d5d1efd8f3e50e +openapi_spec_hash: 924a89b5f031d9215a5a701f834b132f config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index d21f7937..c4d55973 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -520,60 +520,66 @@ def validate( `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. @@ -1093,60 +1099,66 @@ async def validate( `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. prompt: The prompt to use for the TLM call. If not provided, the prompt will be generated from the messages. diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index c6064ed6..2483e669 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -94,60 +94,66 @@ def prompt( `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. @@ -232,60 +238,66 @@ def score( `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. @@ -386,60 +398,66 @@ async def prompt( `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. @@ -524,60 +542,66 @@ async def score( `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 86c10495..48074632 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -123,60 +123,66 @@ class ProjectValidateParams(TypedDict, total=False): `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. """ prompt: Optional[str] @@ -647,6 +653,8 @@ class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_trustworthiness: bool + log: List[str] max_tokens: int diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 8749c5ac..aaa8b321 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -45,60 +45,66 @@ class TlmPromptParams(TypedDict, total=False): `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -110,6 +116,8 @@ class TlmPromptParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_trustworthiness: bool + log: List[str] max_tokens: int diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index 4a0a32ad..a5a75c6d 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -47,60 +47,66 @@ class TlmScoreParams(TypedDict, total=False): `model`, and `max_tokens` is set to 512. You can set custom values for these arguments regardless of the quality preset specified. - Args: model ({"gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", - "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", - "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", - "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", - "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, - default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield - better results, faster models yield faster results). - Models still in beta: - "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", - "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-4.1", "o4-mini", - "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models for low - latency/costs: "gpt-4.1-nano", "nova-micro". - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the TLM response (and in internal trustworthiness scoring). - Higher values here may produce better (more reliable) TLM responses and trustworthiness scores, but at higher runtimes/costs. - If you experience token/rate limit errors while using TLM, try lowering this number. + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - Higher values here can produce more accurate responses from `TLM.prompt()`, but at higher runtimes/costs. - When it is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trustworthiness scoring. - Must be between 0 and 20. Higher values produce more reliable TLM trustworthiness scores, but at higher runtimes/costs. + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. Measuring consistency helps quantify the epistemic uncertainty associated with strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - - num_self_reflections(int, default = 3): the number of self-reflections to perform where the LLM is asked to reflect on the given response and directly evaluate correctness/confidence. - The maximum number of self-reflections currently supported is 3. Lower values will reduce runtimes/costs, but potentially also the reliability of trustworthiness scores. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. Supported similarity measures include - "semantic" (based on natural language inference), "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes/costs. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Higher reasoning efforts may yield more reliable TLM trustworthiness scores. Reduce this value to reduce runtimes/costs. - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. - use_self_reflection (bool, default = `True`): deprecated. Use `num_self_reflections` instead. + disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. + This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. + The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. """ quality_preset: Literal["best", "high", "medium", "low", "base"] @@ -112,6 +118,8 @@ class TlmScoreParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_trustworthiness: bool + log: List[str] max_tokens: int diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 2159a1f0..586b0fa9 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -617,6 +617,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: ], options={ "custom_eval_criteria": [{}], + "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, "model": "model", @@ -1291,6 +1292,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - ], options={ "custom_eval_criteria": [{}], + "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, "model": "model", diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index fd78e60c..fd977f6a 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -33,6 +33,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, "model": "model", @@ -92,6 +93,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, "model": "model", @@ -157,6 +159,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, "model": "model", @@ -216,6 +219,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, "model": "model", From 9159de606715e8232442712d6f9145bd1de4cc1c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 02:08:32 +0000 Subject: [PATCH 226/320] chore(internal): codegen related update --- tests/conftest.py | 8 +- tests/test_client.py | 181 ++++++++++++++++++++++++++++++++----------- 2 files changed, 141 insertions(+), 48 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3472c36d..ed898e38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,6 +45,8 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") +auth_token = "My Auth Token" + @pytest.fixture(scope="session") def client(request: FixtureRequest) -> Iterator[Codex]: @@ -52,7 +54,7 @@ def client(request: FixtureRequest) -> Iterator[Codex]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Codex(base_url=base_url, _strict_response_validation=strict) as client: + with Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=strict) as client: yield client @@ -76,5 +78,7 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncCodex]: else: raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") - async with AsyncCodex(base_url=base_url, _strict_response_validation=strict, http_client=http_client) as client: + async with AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=strict, http_client=http_client + ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 24749152..cdc717fd 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -37,6 +37,7 @@ from .utils import update_env base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") +auth_token = "My Auth Token" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -58,7 +59,7 @@ def _get_open_connections(client: Codex | AsyncCodex) -> int: class TestCodex: - client = Codex(base_url=base_url, _strict_response_validation=True) + client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -84,6 +85,10 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) + copied = self.client.copy(auth_token="another My Auth Token") + assert copied.auth_token == "another My Auth Token" + assert self.client.auth_token == "My Auth Token" + def test_copy_default_options(self) -> None: # options that have a default are overridden correctly copied = self.client.copy(max_retries=7) @@ -101,7 +106,9 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + ) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -133,7 +140,9 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_query={"foo": "bar"} + ) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -257,7 +266,9 @@ def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, timeout=httpx.Timeout(0) + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -266,7 +277,9 @@ def test_client_timeout_option(self) -> None: 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: - client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -274,7 +287,9 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: - client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -282,7 +297,9 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = Codex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -291,16 +308,24 @@ def test_http_client_timeout_option(self) -> None: async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: - Codex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) + Codex( + base_url=base_url, + auth_token=auth_token, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) def test_default_headers_option(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" client2 = Codex( base_url=base_url, + auth_token=auth_token, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -312,7 +337,12 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) + client = Codex( + base_url=base_url, + auth_token=auth_token, + _strict_response_validation=True, + default_query={"query_param": "bar"}, + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -511,7 +541,9 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Codex(base_url="https://example.com/from_init", _strict_response_validation=True) + client = Codex( + base_url="https://example.com/from_init", auth_token=auth_token, _strict_response_validation=True + ) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -520,23 +552,28 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): - client = Codex(_strict_response_validation=True) + client = Codex(auth_token=auth_token, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - Codex(_strict_response_validation=True, environment="production") + Codex(auth_token=auth_token, _strict_response_validation=True, environment="production") - client = Codex(base_url=None, _strict_response_validation=True, environment="production") + client = Codex( + base_url=None, auth_token=auth_token, _strict_response_validation=True, environment="production" + ) assert str(client.base_url).startswith("https://api-codex.cleanlab.ai") @pytest.mark.parametrize( "client", [ - Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Codex( + base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True + ), Codex( base_url="http://localhost:5000/custom/path/", + auth_token=auth_token, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -556,9 +593,12 @@ def test_base_url_trailing_slash(self, client: Codex) -> None: @pytest.mark.parametrize( "client", [ - Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Codex( + base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True + ), Codex( base_url="http://localhost:5000/custom/path/", + auth_token=auth_token, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -578,9 +618,12 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None: @pytest.mark.parametrize( "client", [ - Codex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + Codex( + base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True + ), Codex( base_url="http://localhost:5000/custom/path/", + auth_token=auth_token, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -598,7 +641,7 @@ def test_absolute_request_url(self, client: Codex) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True) + client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -609,7 +652,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True) + client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -630,7 +673,9 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Codex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + Codex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, max_retries=cast(Any, None) + ) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -639,12 +684,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Codex(base_url=base_url, _strict_response_validation=True) + strict_client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Codex(base_url=base_url, _strict_response_validation=False) + client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -672,7 +717,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Codex(base_url=base_url, _strict_response_validation=True) + client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -840,7 +885,7 @@ def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: class TestAsyncCodex: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -868,6 +913,10 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) + copied = self.client.copy(auth_token="another My Auth Token") + assert copied.auth_token == "another My Auth Token" + assert self.client.auth_token == "My Auth Token" + def test_copy_default_options(self) -> None: # options that have a default are overridden correctly copied = self.client.copy(max_retries=7) @@ -885,7 +934,9 @@ def test_copy_default_options(self) -> None: assert isinstance(self.client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + ) assert client.default_headers["X-Foo"] == "bar" # does not override the already given value when not specified @@ -917,7 +968,9 @@ def test_copy_default_headers(self) -> None: client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) def test_copy_default_query(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"foo": "bar"}) + client = AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_query={"foo": "bar"} + ) assert _get_params(client)["foo"] == "bar" # does not override the already given value when not specified @@ -1041,7 +1094,9 @@ async def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) async def test_client_timeout_option(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, timeout=httpx.Timeout(0) + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1050,7 +1105,9 @@ async def test_client_timeout_option(self) -> None: 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: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1058,7 +1115,9 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1066,7 +1125,9 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=http_client) + client = AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, http_client=http_client + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1075,16 +1136,24 @@ async def test_http_client_timeout_option(self) -> None: def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: - AsyncCodex(base_url=base_url, _strict_response_validation=True, http_client=cast(Any, http_client)) + AsyncCodex( + base_url=base_url, + auth_token=auth_token, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) def test_default_headers_option(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_headers={"X-Foo": "bar"}) + client = AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" client2 = AsyncCodex( base_url=base_url, + auth_token=auth_token, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1096,7 +1165,12 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_default_query_option(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True, default_query={"query_param": "bar"}) + client = AsyncCodex( + base_url=base_url, + auth_token=auth_token, + _strict_response_validation=True, + default_query={"query_param": "bar"}, + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) assert dict(url.params) == {"query_param": "bar"} @@ -1295,7 +1369,9 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = AsyncCodex(base_url="https://example.com/from_init", _strict_response_validation=True) + client = AsyncCodex( + base_url="https://example.com/from_init", auth_token=auth_token, _strict_response_validation=True + ) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -1304,23 +1380,28 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): - client = AsyncCodex(_strict_response_validation=True) + client = AsyncCodex(auth_token=auth_token, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" # explicit environment arg requires explicitness with update_env(CODEX_BASE_URL="http://localhost:5000/from/env"): with pytest.raises(ValueError, match=r"you must pass base_url=None"): - AsyncCodex(_strict_response_validation=True, environment="production") + AsyncCodex(auth_token=auth_token, _strict_response_validation=True, environment="production") - client = AsyncCodex(base_url=None, _strict_response_validation=True, environment="production") + client = AsyncCodex( + base_url=None, auth_token=auth_token, _strict_response_validation=True, environment="production" + ) assert str(client.base_url).startswith("https://api-codex.cleanlab.ai") @pytest.mark.parametrize( "client", [ - AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCodex( + base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True + ), AsyncCodex( base_url="http://localhost:5000/custom/path/", + auth_token=auth_token, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1340,9 +1421,12 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: @pytest.mark.parametrize( "client", [ - AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCodex( + base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True + ), AsyncCodex( base_url="http://localhost:5000/custom/path/", + auth_token=auth_token, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1362,9 +1446,12 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: @pytest.mark.parametrize( "client", [ - AsyncCodex(base_url="http://localhost:5000/custom/path/", _strict_response_validation=True), + AsyncCodex( + base_url="http://localhost:5000/custom/path/", auth_token=auth_token, _strict_response_validation=True + ), AsyncCodex( base_url="http://localhost:5000/custom/path/", + auth_token=auth_token, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1382,7 +1469,7 @@ def test_absolute_request_url(self, client: AsyncCodex) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1394,7 +1481,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1416,7 +1503,9 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - AsyncCodex(base_url=base_url, _strict_response_validation=True, max_retries=cast(Any, None)) + AsyncCodex( + base_url=base_url, auth_token=auth_token, _strict_response_validation=True, max_retries=cast(Any, None) + ) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -1426,12 +1515,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + strict_client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncCodex(base_url=base_url, _strict_response_validation=False) + client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1460,7 +1549,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncCodex(base_url=base_url, _strict_response_validation=True) + client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) From aae872824f8c06556f051d14254a5c7ccda1dc0e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:33:40 +0000 Subject: [PATCH 227/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 5e0a177c..b509115f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 924a89b5f031d9215a5a701f834b132f +openapi_spec_hash: 04e1b7aefbeff10daab249b153de147f config_hash: 930284cfa37f835d949c8a1b124f4807 From 27818af513b23232aa26ce0f06dd17ab46b1d67d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:17:51 +0000 Subject: [PATCH 228/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 22 +++++------ src/codex/resources/tlm.py | 44 ++++++++++------------ src/codex/types/project_validate_params.py | 13 ++++--- src/codex/types/tlm_prompt_params.py | 13 ++++--- src/codex/types/tlm_score_params.py | 13 ++++--- tests/api_resources/test_projects.py | 2 + tests/api_resources/test_tlm.py | 4 ++ 8 files changed, 58 insertions(+), 55 deletions(-) diff --git a/.stats.yml b/.stats.yml index b509115f..71f9ac2d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 04e1b7aefbeff10daab249b153de147f +openapi_spec_hash: ad0180a0926e2b6434601e6adf9c322a config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index c4d55973..319097fd 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -527,12 +527,11 @@ def validate( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -1106,12 +1105,11 @@ async def validate( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 2483e669..5d66ec04 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -101,12 +101,11 @@ def prompt( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -245,12 +244,11 @@ def score( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -405,12 +403,11 @@ async def prompt( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -549,12 +546,11 @@ async def score( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 48074632..719ad3d3 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -130,12 +130,11 @@ class ProjectValidateParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -653,6 +652,8 @@ class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_persistence: bool + disable_trustworthiness: bool log: List[str] diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index aaa8b321..821c3811 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -52,12 +52,11 @@ class TlmPromptParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -116,6 +115,8 @@ class TlmPromptParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_persistence: bool + disable_trustworthiness: bool log: List[str] diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index a5a75c6d..d676a1d6 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -54,12 +54,11 @@ class TlmScoreParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -118,6 +117,8 @@ class TlmScoreParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_persistence: bool + disable_trustworthiness: bool log: List[str] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 586b0fa9..04eef999 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -617,6 +617,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: ], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -1292,6 +1293,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - ], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index fd977f6a..6c8c1770 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -33,6 +33,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -93,6 +94,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -159,6 +161,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -219,6 +222,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, From 9d9c11651a7641c5d78134cefc2d3184c414feb1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 03:58:36 +0000 Subject: [PATCH 229/320] chore: update github action --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6979ebb..40152386 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: ./scripts/lint build: - if: github.repository == 'stainless-sdks/codex-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork timeout-minutes: 10 name: build permissions: @@ -61,12 +61,14 @@ jobs: run: rye build - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/codex-python' id: github-oidc uses: actions/github-script@v6 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball + if: github.repository == 'stainless-sdks/codex-python' env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} From dda45366715d816234f8b95e20042c922c1415d6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:23:00 +0000 Subject: [PATCH 230/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_validate_response.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 71f9ac2d..b94f7cf2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: ad0180a0926e2b6434601e6adf9c322a +openapi_spec_hash: 7daf4896ba4932714f8fe4fff277d7c7 config_hash: 930284cfa37f835d949c8a1b124f4807 diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index 44883119..003b676c 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -59,6 +59,9 @@ class ProjectValidateResponse(BaseModel): to answer, if it does not already exist. """ + log_id: str + """The UUID of the query log entry created for this validation request.""" + should_guardrail: bool """ True if the response should be guardrailed by the AI system, False if the From 1bdd9e50878631ff3e603160473d9844b2873c7d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:34:12 +0000 Subject: [PATCH 231/320] feat(api): add user feedback --- .stats.yml | 4 +- api.md | 2 + src/codex/resources/projects/query_logs.py | 102 +++++++++++++++- src/codex/types/projects/__init__.py | 2 + .../query_log_add_user_feedback_params.py | 14 +++ .../query_log_add_user_feedback_response.py | 11 ++ .../api_resources/projects/test_query_logs.py | 115 ++++++++++++++++++ 7 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/codex/types/projects/query_log_add_user_feedback_params.py create mode 100644 src/codex/types/projects/query_log_add_user_feedback_response.py diff --git a/.stats.yml b/.stats.yml index b94f7cf2..890def24 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 54 +configured_endpoints: 55 openapi_spec_hash: 7daf4896ba4932714f8fe4fff277d7c7 -config_hash: 930284cfa37f835d949c8a1b124f4807 +config_hash: bed87752f4056d0c4bf2ddf856307800 diff --git a/api.md b/api.md index 1646d0b9..693ee75f 100644 --- a/api.md +++ b/api.md @@ -202,6 +202,7 @@ Types: from codex.types.projects import ( QueryLogRetrieveResponse, QueryLogListResponse, + QueryLogAddUserFeedbackResponse, QueryLogListByGroupResponse, QueryLogListGroupsResponse, QueryLogStartRemediationResponse, @@ -212,6 +213,7 @@ Methods: - client.projects.query_logs.retrieve(query_log_id, \*, project_id) -> QueryLogRetrieveResponse - client.projects.query_logs.list(project_id, \*\*params) -> SyncOffsetPageQueryLogs[QueryLogListResponse] +- client.projects.query_logs.add_user_feedback(query_log_id, \*, project_id, \*\*params) -> QueryLogAddUserFeedbackResponse - client.projects.query_logs.list_by_group(project_id, \*\*params) -> QueryLogListByGroupResponse - client.projects.query_logs.list_groups(project_id, \*\*params) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse] - client.projects.query_logs.start_remediation(query_log_id, \*, project_id) -> QueryLogStartRemediationResponse diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 45277433..e3b17abf 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -25,11 +25,17 @@ AsyncOffsetPageQueryLogGroups, ) from ..._base_client import AsyncPaginator, make_request_options -from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params +from ...types.projects import ( + query_log_list_params, + query_log_list_groups_params, + query_log_list_by_group_params, + query_log_add_user_feedback_params, +) from ...types.projects.query_log_list_response import QueryLogListResponse from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse from ...types.projects.query_log_list_groups_response import QueryLogListGroupsResponse from ...types.projects.query_log_list_by_group_response import QueryLogListByGroupResponse +from ...types.projects.query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse from ...types.projects.query_log_start_remediation_response import QueryLogStartRemediationResponse __all__ = ["QueryLogsResource", "AsyncQueryLogsResource"] @@ -184,6 +190,46 @@ def list( model=QueryLogListResponse, ) + def add_user_feedback( + self, + query_log_id: str, + *, + project_id: str, + key: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> QueryLogAddUserFeedbackResponse: + """ + Add User Feedback Route + + Args: + key: A key describing the criteria of the feedback, eg 'rating' + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return self._post( + f"/api/projects/{project_id}/query_logs/{query_log_id}/user_feedback", + body=maybe_transform({"key": key}, query_log_add_user_feedback_params.QueryLogAddUserFeedbackParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogAddUserFeedbackResponse, + ) + def list_by_group( self, project_id: str, @@ -568,6 +614,48 @@ def list( model=QueryLogListResponse, ) + async def add_user_feedback( + self, + query_log_id: str, + *, + project_id: str, + key: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> QueryLogAddUserFeedbackResponse: + """ + Add User Feedback Route + + Args: + key: A key describing the criteria of the feedback, eg 'rating' + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return await self._post( + f"/api/projects/{project_id}/query_logs/{query_log_id}/user_feedback", + body=await async_maybe_transform( + {"key": key}, query_log_add_user_feedback_params.QueryLogAddUserFeedbackParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogAddUserFeedbackResponse, + ) + async def list_by_group( self, project_id: str, @@ -813,6 +901,9 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.list = to_raw_response_wrapper( query_logs.list, ) + self.add_user_feedback = to_raw_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = to_raw_response_wrapper( query_logs.list_by_group, ) @@ -834,6 +925,9 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.list = async_to_raw_response_wrapper( query_logs.list, ) + self.add_user_feedback = async_to_raw_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = async_to_raw_response_wrapper( query_logs.list_by_group, ) @@ -855,6 +949,9 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.list = to_streamed_response_wrapper( query_logs.list, ) + self.add_user_feedback = to_streamed_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = to_streamed_response_wrapper( query_logs.list_by_group, ) @@ -876,6 +973,9 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.list = async_to_streamed_response_wrapper( query_logs.list, ) + self.add_user_feedback = async_to_streamed_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = async_to_streamed_response_wrapper( query_logs.list_by_group, ) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index cb2989f3..b90cbd89 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -27,6 +27,8 @@ from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams from .query_log_list_by_group_response import QueryLogListByGroupResponse as QueryLogListByGroupResponse from .remediation_edit_answer_response import RemediationEditAnswerResponse as RemediationEditAnswerResponse +from .query_log_add_user_feedback_params import QueryLogAddUserFeedbackParams as QueryLogAddUserFeedbackParams +from .query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse as QueryLogAddUserFeedbackResponse from .query_log_start_remediation_response import QueryLogStartRemediationResponse as QueryLogStartRemediationResponse from .remediation_edit_draft_answer_params import RemediationEditDraftAnswerParams as RemediationEditDraftAnswerParams from .remediation_edit_draft_answer_response import ( diff --git a/src/codex/types/projects/query_log_add_user_feedback_params.py b/src/codex/types/projects/query_log_add_user_feedback_params.py new file mode 100644 index 00000000..e8418924 --- /dev/null +++ b/src/codex/types/projects/query_log_add_user_feedback_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["QueryLogAddUserFeedbackParams"] + + +class QueryLogAddUserFeedbackParams(TypedDict, total=False): + project_id: Required[str] + + key: Required[str] + """A key describing the criteria of the feedback, eg 'rating'""" diff --git a/src/codex/types/projects/query_log_add_user_feedback_response.py b/src/codex/types/projects/query_log_add_user_feedback_response.py new file mode 100644 index 00000000..adec25fb --- /dev/null +++ b/src/codex/types/projects/query_log_add_user_feedback_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["QueryLogAddUserFeedbackResponse"] + + +class QueryLogAddUserFeedbackResponse(BaseModel): + custom_metadata: object + + query_log_id: str diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 5f7e02cd..05d3f46e 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -21,6 +21,7 @@ QueryLogRetrieveResponse, QueryLogListGroupsResponse, QueryLogListByGroupResponse, + QueryLogAddUserFeedbackResponse, QueryLogStartRemediationResponse, ) @@ -146,6 +147,63 @@ def test_path_params_list(self, client: Codex) -> None: project_id="", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_add_user_feedback(self, client: Codex) -> None: + query_log = client.projects.query_logs.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_add_user_feedback(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_add_user_feedback(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_add_user_feedback(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + key="key", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_by_group(self, client: Codex) -> None: @@ -451,6 +509,63 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None: project_id="", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_add_user_feedback(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_add_user_feedback(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_add_user_feedback(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_add_user_feedback(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + key="key", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_by_group(self, async_client: AsyncCodex) -> None: From 364b9fb39cd14c1839517d403bcb6ca76cc0e09c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:39:29 +0000 Subject: [PATCH 232/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 380b6f91..3188cedb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.24" + ".": "0.1.0-alpha.25" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index cc023e90..f077e092 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.24" +version = "0.1.0-alpha.25" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index e020cb91..656ce65e 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.24" # x-release-please-version +__version__ = "0.1.0-alpha.25" # x-release-please-version From 370ce17928943eaf7c0eaaa9d36d816828b69613 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 23:17:48 +0000 Subject: [PATCH 233/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/projects/query_log_list_groups_response.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 890def24..c5d767fa 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 7daf4896ba4932714f8fe4fff277d7c7 +openapi_spec_hash: d69252c7252423bc98ecc17807eb33ec config_hash: bed87752f4056d0c4bf2ddf856307800 diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 7b2d44c9..7dbf1929 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -337,6 +337,9 @@ class QueryLogListGroupsResponse(BaseModel): formatted_original_question: Optional[str] = None + impact_score: float + """Impact score used for prioritization sorting""" + is_bad_response: bool needs_review: bool From ee97fd9eb6716e792b30ee51b9db46f0bf95ecf4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 01:17:37 +0000 Subject: [PATCH 234/320] feat(api): api update --- .stats.yml | 4 +- api.md | 2 - src/codex/resources/projects/projects.py | 22 ++-- src/codex/resources/projects/query_logs.py | 102 +--------------- src/codex/resources/tlm.py | 44 ++++--- src/codex/types/project_validate_params.py | 13 +- src/codex/types/project_validate_response.py | 3 - src/codex/types/projects/__init__.py | 2 - .../query_log_add_user_feedback_params.py | 14 --- .../query_log_add_user_feedback_response.py | 11 -- .../query_log_list_groups_response.py | 3 - src/codex/types/tlm_prompt_params.py | 13 +- src/codex/types/tlm_score_params.py | 13 +- .../api_resources/projects/test_query_logs.py | 115 ------------------ tests/api_resources/test_projects.py | 2 - tests/api_resources/test_tlm.py | 4 - 16 files changed, 57 insertions(+), 310 deletions(-) delete mode 100644 src/codex/types/projects/query_log_add_user_feedback_params.py delete mode 100644 src/codex/types/projects/query_log_add_user_feedback_response.py diff --git a/.stats.yml b/.stats.yml index c5d767fa..362b30b1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 55 -openapi_spec_hash: d69252c7252423bc98ecc17807eb33ec +configured_endpoints: 54 +openapi_spec_hash: 04e1b7aefbeff10daab249b153de147f config_hash: bed87752f4056d0c4bf2ddf856307800 diff --git a/api.md b/api.md index 693ee75f..1646d0b9 100644 --- a/api.md +++ b/api.md @@ -202,7 +202,6 @@ Types: from codex.types.projects import ( QueryLogRetrieveResponse, QueryLogListResponse, - QueryLogAddUserFeedbackResponse, QueryLogListByGroupResponse, QueryLogListGroupsResponse, QueryLogStartRemediationResponse, @@ -213,7 +212,6 @@ Methods: - client.projects.query_logs.retrieve(query_log_id, \*, project_id) -> QueryLogRetrieveResponse - client.projects.query_logs.list(project_id, \*\*params) -> SyncOffsetPageQueryLogs[QueryLogListResponse] -- client.projects.query_logs.add_user_feedback(query_log_id, \*, project_id, \*\*params) -> QueryLogAddUserFeedbackResponse - client.projects.query_logs.list_by_group(project_id, \*\*params) -> QueryLogListByGroupResponse - client.projects.query_logs.list_groups(project_id, \*\*params) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse] - client.projects.query_logs.start_remediation(query_log_id, \*, project_id) -> QueryLogStartRemediationResponse diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 319097fd..c4d55973 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -527,11 +527,12 @@ def validate( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -1105,11 +1106,12 @@ async def validate( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index e3b17abf..45277433 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -25,17 +25,11 @@ AsyncOffsetPageQueryLogGroups, ) from ..._base_client import AsyncPaginator, make_request_options -from ...types.projects import ( - query_log_list_params, - query_log_list_groups_params, - query_log_list_by_group_params, - query_log_add_user_feedback_params, -) +from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params from ...types.projects.query_log_list_response import QueryLogListResponse from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse from ...types.projects.query_log_list_groups_response import QueryLogListGroupsResponse from ...types.projects.query_log_list_by_group_response import QueryLogListByGroupResponse -from ...types.projects.query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse from ...types.projects.query_log_start_remediation_response import QueryLogStartRemediationResponse __all__ = ["QueryLogsResource", "AsyncQueryLogsResource"] @@ -190,46 +184,6 @@ def list( model=QueryLogListResponse, ) - def add_user_feedback( - self, - query_log_id: str, - *, - project_id: str, - key: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> QueryLogAddUserFeedbackResponse: - """ - Add User Feedback Route - - Args: - key: A key describing the criteria of the feedback, eg 'rating' - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not query_log_id: - raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") - return self._post( - f"/api/projects/{project_id}/query_logs/{query_log_id}/user_feedback", - body=maybe_transform({"key": key}, query_log_add_user_feedback_params.QueryLogAddUserFeedbackParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=QueryLogAddUserFeedbackResponse, - ) - def list_by_group( self, project_id: str, @@ -614,48 +568,6 @@ def list( model=QueryLogListResponse, ) - async def add_user_feedback( - self, - query_log_id: str, - *, - project_id: str, - key: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> QueryLogAddUserFeedbackResponse: - """ - Add User Feedback Route - - Args: - key: A key describing the criteria of the feedback, eg 'rating' - - 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 project_id: - raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") - if not query_log_id: - raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") - return await self._post( - f"/api/projects/{project_id}/query_logs/{query_log_id}/user_feedback", - body=await async_maybe_transform( - {"key": key}, query_log_add_user_feedback_params.QueryLogAddUserFeedbackParams - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=QueryLogAddUserFeedbackResponse, - ) - async def list_by_group( self, project_id: str, @@ -901,9 +813,6 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.list = to_raw_response_wrapper( query_logs.list, ) - self.add_user_feedback = to_raw_response_wrapper( - query_logs.add_user_feedback, - ) self.list_by_group = to_raw_response_wrapper( query_logs.list_by_group, ) @@ -925,9 +834,6 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.list = async_to_raw_response_wrapper( query_logs.list, ) - self.add_user_feedback = async_to_raw_response_wrapper( - query_logs.add_user_feedback, - ) self.list_by_group = async_to_raw_response_wrapper( query_logs.list_by_group, ) @@ -949,9 +855,6 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.list = to_streamed_response_wrapper( query_logs.list, ) - self.add_user_feedback = to_streamed_response_wrapper( - query_logs.add_user_feedback, - ) self.list_by_group = to_streamed_response_wrapper( query_logs.list_by_group, ) @@ -973,9 +876,6 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.list = async_to_streamed_response_wrapper( query_logs.list, ) - self.add_user_feedback = async_to_streamed_response_wrapper( - query_logs.add_user_feedback, - ) self.list_by_group = async_to_streamed_response_wrapper( query_logs.list_by_group, ) diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 5d66ec04..2483e669 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -101,11 +101,12 @@ def prompt( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -244,11 +245,12 @@ def score( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -403,11 +405,12 @@ async def prompt( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -546,11 +549,12 @@ async def score( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 719ad3d3..48074632 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -130,11 +130,12 @@ class ProjectValidateParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -652,8 +653,6 @@ class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] - disable_persistence: bool - disable_trustworthiness: bool log: List[str] diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index 003b676c..44883119 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -59,9 +59,6 @@ class ProjectValidateResponse(BaseModel): to answer, if it does not already exist. """ - log_id: str - """The UUID of the query log entry created for this validation request.""" - should_guardrail: bool """ True if the response should be guardrailed by the AI system, False if the diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index b90cbd89..cb2989f3 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -27,8 +27,6 @@ from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams from .query_log_list_by_group_response import QueryLogListByGroupResponse as QueryLogListByGroupResponse from .remediation_edit_answer_response import RemediationEditAnswerResponse as RemediationEditAnswerResponse -from .query_log_add_user_feedback_params import QueryLogAddUserFeedbackParams as QueryLogAddUserFeedbackParams -from .query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse as QueryLogAddUserFeedbackResponse from .query_log_start_remediation_response import QueryLogStartRemediationResponse as QueryLogStartRemediationResponse from .remediation_edit_draft_answer_params import RemediationEditDraftAnswerParams as RemediationEditDraftAnswerParams from .remediation_edit_draft_answer_response import ( diff --git a/src/codex/types/projects/query_log_add_user_feedback_params.py b/src/codex/types/projects/query_log_add_user_feedback_params.py deleted file mode 100644 index e8418924..00000000 --- a/src/codex/types/projects/query_log_add_user_feedback_params.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, TypedDict - -__all__ = ["QueryLogAddUserFeedbackParams"] - - -class QueryLogAddUserFeedbackParams(TypedDict, total=False): - project_id: Required[str] - - key: Required[str] - """A key describing the criteria of the feedback, eg 'rating'""" diff --git a/src/codex/types/projects/query_log_add_user_feedback_response.py b/src/codex/types/projects/query_log_add_user_feedback_response.py deleted file mode 100644 index adec25fb..00000000 --- a/src/codex/types/projects/query_log_add_user_feedback_response.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from ..._models import BaseModel - -__all__ = ["QueryLogAddUserFeedbackResponse"] - - -class QueryLogAddUserFeedbackResponse(BaseModel): - custom_metadata: object - - query_log_id: str diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 7dbf1929..7b2d44c9 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -337,9 +337,6 @@ class QueryLogListGroupsResponse(BaseModel): formatted_original_question: Optional[str] = None - impact_score: float - """Impact score used for prioritization sorting""" - is_bad_response: bool needs_review: bool diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 821c3811..aaa8b321 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -52,11 +52,12 @@ class TlmPromptParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -115,8 +116,6 @@ class TlmPromptParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] - disable_persistence: bool - disable_trustworthiness: bool log: List[str] diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index d676a1d6..a5a75c6d 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -54,11 +54,12 @@ class TlmScoreParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". + Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", + "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended + models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", + "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", + "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -117,8 +118,6 @@ class TlmScoreParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] - disable_persistence: bool - disable_trustworthiness: bool log: List[str] diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 05d3f46e..5f7e02cd 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -21,7 +21,6 @@ QueryLogRetrieveResponse, QueryLogListGroupsResponse, QueryLogListByGroupResponse, - QueryLogAddUserFeedbackResponse, QueryLogStartRemediationResponse, ) @@ -147,63 +146,6 @@ def test_path_params_list(self, client: Codex) -> None: project_id="", ) - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_add_user_feedback(self, client: Codex) -> None: - query_log = client.projects.query_logs.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) - assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_add_user_feedback(self, client: Codex) -> None: - response = client.projects.query_logs.with_raw_response.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = response.parse() - assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_add_user_feedback(self, client: Codex) -> None: - with client.projects.query_logs.with_streaming_response.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - query_log = response.parse() - assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_path_params_add_user_feedback(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.query_logs.with_raw_response.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - key="key", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): - client.projects.query_logs.with_raw_response.add_user_feedback( - query_log_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_by_group(self, client: Codex) -> None: @@ -509,63 +451,6 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None: project_id="", ) - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_add_user_feedback(self, async_client: AsyncCodex) -> None: - query_log = await async_client.projects.query_logs.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) - assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_add_user_feedback(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.query_logs.with_raw_response.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = await response.parse() - assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_add_user_feedback(self, async_client: AsyncCodex) -> None: - async with async_client.projects.query_logs.with_streaming_response.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - query_log = await response.parse() - assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_path_params_add_user_feedback(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.query_logs.with_raw_response.add_user_feedback( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - key="key", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): - await async_client.projects.query_logs.with_raw_response.add_user_feedback( - query_log_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - key="key", - ) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_by_group(self, async_client: AsyncCodex) -> None: diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 04eef999..586b0fa9 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -617,7 +617,6 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: ], options={ "custom_eval_criteria": [{}], - "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -1293,7 +1292,6 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - ], options={ "custom_eval_criteria": [{}], - "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index 6c8c1770..fd977f6a 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -33,7 +33,6 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], - "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -94,7 +93,6 @@ def test_method_score_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], - "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -161,7 +159,6 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], - "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -222,7 +219,6 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], - "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, From f37d7f30f3ab58cb4d4d3bedb9915a32528e86b2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 03:45:14 +0000 Subject: [PATCH 235/320] chore(internal): change ci workflow machines --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40152386..49f178b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: permissions: contents: read id-token: write - runs-on: depot-ubuntu-24.04 + runs-on: ${{ github.repository == 'stainless-sdks/codex-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 From 55850fea9c5c1976fb5f25de680b0f7f0be41f20 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 17:17:45 +0000 Subject: [PATCH 236/320] feat(api): api update --- .stats.yml | 4 +- api.md | 2 + src/codex/resources/projects/projects.py | 22 ++-- src/codex/resources/projects/query_logs.py | 102 +++++++++++++++- src/codex/resources/tlm.py | 44 +++---- src/codex/types/project_validate_params.py | 13 +- src/codex/types/project_validate_response.py | 3 + src/codex/types/projects/__init__.py | 2 + .../query_log_add_user_feedback_params.py | 14 +++ .../query_log_add_user_feedback_response.py | 11 ++ .../query_log_list_groups_response.py | 3 + src/codex/types/tlm_prompt_params.py | 13 +- src/codex/types/tlm_score_params.py | 13 +- .../api_resources/projects/test_query_logs.py | 115 ++++++++++++++++++ tests/api_resources/test_projects.py | 2 + tests/api_resources/test_tlm.py | 4 + 16 files changed, 310 insertions(+), 57 deletions(-) create mode 100644 src/codex/types/projects/query_log_add_user_feedback_params.py create mode 100644 src/codex/types/projects/query_log_add_user_feedback_response.py diff --git a/.stats.yml b/.stats.yml index 362b30b1..c5d767fa 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 54 -openapi_spec_hash: 04e1b7aefbeff10daab249b153de147f +configured_endpoints: 55 +openapi_spec_hash: d69252c7252423bc98ecc17807eb33ec config_hash: bed87752f4056d0c4bf2ddf856307800 diff --git a/api.md b/api.md index 1646d0b9..693ee75f 100644 --- a/api.md +++ b/api.md @@ -202,6 +202,7 @@ Types: from codex.types.projects import ( QueryLogRetrieveResponse, QueryLogListResponse, + QueryLogAddUserFeedbackResponse, QueryLogListByGroupResponse, QueryLogListGroupsResponse, QueryLogStartRemediationResponse, @@ -212,6 +213,7 @@ Methods: - client.projects.query_logs.retrieve(query_log_id, \*, project_id) -> QueryLogRetrieveResponse - client.projects.query_logs.list(project_id, \*\*params) -> SyncOffsetPageQueryLogs[QueryLogListResponse] +- client.projects.query_logs.add_user_feedback(query_log_id, \*, project_id, \*\*params) -> QueryLogAddUserFeedbackResponse - client.projects.query_logs.list_by_group(project_id, \*\*params) -> QueryLogListByGroupResponse - client.projects.query_logs.list_groups(project_id, \*\*params) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse] - client.projects.query_logs.start_remediation(query_log_id, \*, project_id) -> QueryLogStartRemediationResponse diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index c4d55973..319097fd 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -527,12 +527,11 @@ def validate( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -1106,12 +1105,11 @@ async def validate( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 45277433..e3b17abf 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -25,11 +25,17 @@ AsyncOffsetPageQueryLogGroups, ) from ..._base_client import AsyncPaginator, make_request_options -from ...types.projects import query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params +from ...types.projects import ( + query_log_list_params, + query_log_list_groups_params, + query_log_list_by_group_params, + query_log_add_user_feedback_params, +) from ...types.projects.query_log_list_response import QueryLogListResponse from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse from ...types.projects.query_log_list_groups_response import QueryLogListGroupsResponse from ...types.projects.query_log_list_by_group_response import QueryLogListByGroupResponse +from ...types.projects.query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse from ...types.projects.query_log_start_remediation_response import QueryLogStartRemediationResponse __all__ = ["QueryLogsResource", "AsyncQueryLogsResource"] @@ -184,6 +190,46 @@ def list( model=QueryLogListResponse, ) + def add_user_feedback( + self, + query_log_id: str, + *, + project_id: str, + key: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> QueryLogAddUserFeedbackResponse: + """ + Add User Feedback Route + + Args: + key: A key describing the criteria of the feedback, eg 'rating' + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return self._post( + f"/api/projects/{project_id}/query_logs/{query_log_id}/user_feedback", + body=maybe_transform({"key": key}, query_log_add_user_feedback_params.QueryLogAddUserFeedbackParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogAddUserFeedbackResponse, + ) + def list_by_group( self, project_id: str, @@ -568,6 +614,48 @@ def list( model=QueryLogListResponse, ) + async def add_user_feedback( + self, + query_log_id: str, + *, + project_id: str, + key: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> QueryLogAddUserFeedbackResponse: + """ + Add User Feedback Route + + Args: + key: A key describing the criteria of the feedback, eg 'rating' + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return await self._post( + f"/api/projects/{project_id}/query_logs/{query_log_id}/user_feedback", + body=await async_maybe_transform( + {"key": key}, query_log_add_user_feedback_params.QueryLogAddUserFeedbackParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogAddUserFeedbackResponse, + ) + async def list_by_group( self, project_id: str, @@ -813,6 +901,9 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.list = to_raw_response_wrapper( query_logs.list, ) + self.add_user_feedback = to_raw_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = to_raw_response_wrapper( query_logs.list_by_group, ) @@ -834,6 +925,9 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.list = async_to_raw_response_wrapper( query_logs.list, ) + self.add_user_feedback = async_to_raw_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = async_to_raw_response_wrapper( query_logs.list_by_group, ) @@ -855,6 +949,9 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.list = to_streamed_response_wrapper( query_logs.list, ) + self.add_user_feedback = to_streamed_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = to_streamed_response_wrapper( query_logs.list_by_group, ) @@ -876,6 +973,9 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.list = async_to_streamed_response_wrapper( query_logs.list, ) + self.add_user_feedback = async_to_streamed_response_wrapper( + query_logs.add_user_feedback, + ) self.list_by_group = async_to_streamed_response_wrapper( query_logs.list_by_group, ) diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 2483e669..5d66ec04 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -101,12 +101,11 @@ def prompt( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -245,12 +244,11 @@ def score( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -405,12 +403,11 @@ async def prompt( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -549,12 +546,11 @@ async def score( "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 48074632..719ad3d3 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -130,12 +130,11 @@ class ProjectValidateParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -653,6 +652,8 @@ class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_persistence: bool + disable_trustworthiness: bool log: List[str] diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index 44883119..003b676c 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -59,6 +59,9 @@ class ProjectValidateResponse(BaseModel): to answer, if it does not already exist. """ + log_id: str + """The UUID of the query log entry created for this validation request.""" + should_guardrail: bool """ True if the response should be guardrailed by the AI system, False if the diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index cb2989f3..b90cbd89 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -27,6 +27,8 @@ from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams from .query_log_list_by_group_response import QueryLogListByGroupResponse as QueryLogListByGroupResponse from .remediation_edit_answer_response import RemediationEditAnswerResponse as RemediationEditAnswerResponse +from .query_log_add_user_feedback_params import QueryLogAddUserFeedbackParams as QueryLogAddUserFeedbackParams +from .query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse as QueryLogAddUserFeedbackResponse from .query_log_start_remediation_response import QueryLogStartRemediationResponse as QueryLogStartRemediationResponse from .remediation_edit_draft_answer_params import RemediationEditDraftAnswerParams as RemediationEditDraftAnswerParams from .remediation_edit_draft_answer_response import ( diff --git a/src/codex/types/projects/query_log_add_user_feedback_params.py b/src/codex/types/projects/query_log_add_user_feedback_params.py new file mode 100644 index 00000000..e8418924 --- /dev/null +++ b/src/codex/types/projects/query_log_add_user_feedback_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["QueryLogAddUserFeedbackParams"] + + +class QueryLogAddUserFeedbackParams(TypedDict, total=False): + project_id: Required[str] + + key: Required[str] + """A key describing the criteria of the feedback, eg 'rating'""" diff --git a/src/codex/types/projects/query_log_add_user_feedback_response.py b/src/codex/types/projects/query_log_add_user_feedback_response.py new file mode 100644 index 00000000..adec25fb --- /dev/null +++ b/src/codex/types/projects/query_log_add_user_feedback_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["QueryLogAddUserFeedbackResponse"] + + +class QueryLogAddUserFeedbackResponse(BaseModel): + custom_metadata: object + + query_log_id: str diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 7b2d44c9..7dbf1929 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -337,6 +337,9 @@ class QueryLogListGroupsResponse(BaseModel): formatted_original_question: Optional[str] = None + impact_score: float + """Impact score used for prioritization sorting""" + is_bad_response: bool needs_review: bool diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index aaa8b321..821c3811 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -52,12 +52,11 @@ class TlmPromptParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -116,6 +115,8 @@ class TlmPromptParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_persistence: bool + disable_trustworthiness: bool log: List[str] diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index a5a75c6d..d676a1d6 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -54,12 +54,11 @@ class TlmScoreParams(TypedDict, total=False): "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). - - Models still in beta: "gpt-5", "gpt-5-mini", "gpt-5-nano", "o3", "o1", - "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". - Recommended - models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", - "claude-sonnet-4-0". - Recommended models for low latency/costs: "gpt-4.1-nano", - "nova-micro". + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. @@ -118,6 +117,8 @@ class TlmScoreParams(TypedDict, total=False): class Options(TypedDict, total=False): custom_eval_criteria: Iterable[object] + disable_persistence: bool + disable_trustworthiness: bool log: List[str] diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 5f7e02cd..05d3f46e 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -21,6 +21,7 @@ QueryLogRetrieveResponse, QueryLogListGroupsResponse, QueryLogListByGroupResponse, + QueryLogAddUserFeedbackResponse, QueryLogStartRemediationResponse, ) @@ -146,6 +147,63 @@ def test_path_params_list(self, client: Codex) -> None: project_id="", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_add_user_feedback(self, client: Codex) -> None: + query_log = client.projects.query_logs.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_add_user_feedback(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_add_user_feedback(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_add_user_feedback(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + key="key", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_by_group(self, client: Codex) -> None: @@ -451,6 +509,63 @@ async def test_path_params_list(self, async_client: AsyncCodex) -> None: project_id="", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_add_user_feedback(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_add_user_feedback(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_add_user_feedback(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogAddUserFeedbackResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_add_user_feedback(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + key="key", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.add_user_feedback( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + key="key", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_by_group(self, async_client: AsyncCodex) -> None: diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 586b0fa9..04eef999 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -617,6 +617,7 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: ], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -1292,6 +1293,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - ], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py index fd977f6a..6c8c1770 100644 --- a/tests/api_resources/test_tlm.py +++ b/tests/api_resources/test_tlm.py @@ -33,6 +33,7 @@ def test_method_prompt_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -93,6 +94,7 @@ def test_method_score_with_all_params(self, client: Codex) -> None: constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -159,6 +161,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, @@ -219,6 +222,7 @@ async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> N constrain_outputs=["string"], options={ "custom_eval_criteria": [{}], + "disable_persistence": True, "disable_trustworthiness": True, "log": ["string"], "max_tokens": 0, From f64c7cd8fa7bbf9cbc11d79e221aa553aa865b59 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 04:06:05 +0000 Subject: [PATCH 237/320] fix: avoid newer type syntax --- src/codex/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index b8387ce9..92f7c10b 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -304,7 +304,7 @@ def model_dump( exclude_none=exclude_none, ) - return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped @override def model_dump_json( From e6be71a86ae54d05a4ae3097465867204845d534 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 04:37:56 +0000 Subject: [PATCH 238/320] chore(internal): update pyright exclude list --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index f077e092..86e4b99e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,6 +148,7 @@ exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true From 2cd5d21a6f8c3ce6d7870cef8b72c2b3e4c625c1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 18:17:31 +0000 Subject: [PATCH 239/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 52 +++++++++++++++++-- .../query_log_list_by_group_params.py | 12 ++++- .../types/projects/query_log_list_params.py | 12 ++++- 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index c5d767fa..1be05f48 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: d69252c7252423bc98ecc17807eb33ec +openapi_spec_hash: b260cce23d53dba37b982654e7928405 config_hash: bed87752f4056d0c4bf2ddf856307800 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index e3b17abf..6bbd7487 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -115,7 +115,18 @@ def list( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + sort: Optional[ + Literal[ + "created_at", + "primary_eval_issue_score", + "score_trustworthiness", + "score_context_sufficiency", + "score_response_helpfulness", + "score_query_ease", + "score_response_groundedness", + ] + ] + | NotGiven = NOT_GIVEN, tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -250,7 +261,18 @@ def list_by_group( ] | NotGiven = NOT_GIVEN, remediation_ids: List[str] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + sort: Optional[ + Literal[ + "created_at", + "primary_eval_issue_score", + "score_trustworthiness", + "score_context_sufficiency", + "score_response_helpfulness", + "score_query_ease", + "score_response_groundedness", + ] + ] + | NotGiven = NOT_GIVEN, tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -539,7 +561,18 @@ def list( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + sort: Optional[ + Literal[ + "created_at", + "primary_eval_issue_score", + "score_trustworthiness", + "score_context_sufficiency", + "score_response_helpfulness", + "score_query_ease", + "score_response_groundedness", + ] + ] + | NotGiven = NOT_GIVEN, tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -676,7 +709,18 @@ async def list_by_group( ] | NotGiven = NOT_GIVEN, remediation_ids: List[str] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score"]] | NotGiven = NOT_GIVEN, + sort: Optional[ + Literal[ + "created_at", + "primary_eval_issue_score", + "score_trustworthiness", + "score_context_sufficiency", + "score_response_helpfulness", + "score_query_ease", + "score_response_groundedness", + ] + ] + | NotGiven = NOT_GIVEN, tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 0fbb2804..5ca6d107 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -50,7 +50,17 @@ class QueryLogListByGroupParams(TypedDict, total=False): remediation_ids: List[str] """List of groups to list child logs for""" - sort: Optional[Literal["created_at", "primary_eval_issue_score"]] + sort: Optional[ + Literal[ + "created_at", + "primary_eval_issue_score", + "score_trustworthiness", + "score_context_sufficiency", + "score_response_helpfulness", + "score_query_ease", + "score_response_groundedness", + ] + ] tool_call_names: Optional[List[str]] """Filter by names of tools called in the assistant response""" diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 02c1707b..2da646f8 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -44,7 +44,17 @@ class QueryLogListParams(TypedDict, total=False): ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" - sort: Optional[Literal["created_at", "primary_eval_issue_score"]] + sort: Optional[ + Literal[ + "created_at", + "primary_eval_issue_score", + "score_trustworthiness", + "score_context_sufficiency", + "score_response_helpfulness", + "score_query_ease", + "score_response_groundedness", + ] + ] tool_call_names: Optional[List[str]] """Filter by names of tools called in the assistant response""" From 28c16a81969d416a4a2bec9867d956893bdaa48d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 02:42:24 +0000 Subject: [PATCH 240/320] chore(internal): add Sequence related utils --- src/codex/_types.py | 36 +++++++++++++++++++++++++++++++++++- src/codex/_utils/__init__.py | 1 + src/codex/_utils/_typing.py | 5 +++++ tests/utils.py | 10 +++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/codex/_types.py b/src/codex/_types.py index f2d17a1f..99d66e20 100644 --- a/src/codex/_types.py +++ b/src/codex/_types.py @@ -13,10 +13,21 @@ Mapping, TypeVar, Callable, + Iterator, Optional, Sequence, ) -from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, +) import httpx import pydantic @@ -217,3 +228,26 @@ 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 + 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 index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... + def count(self, value: Any, /) -> int: ... + 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/codex/_utils/__init__.py b/src/codex/_utils/__init__.py index d4fda26f..ca547ce5 100644 --- a/src/codex/_utils/__init__.py +++ b/src/codex/_utils/__init__.py @@ -38,6 +38,7 @@ 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, diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py index 1bac9542..845cd6b2 100644 --- a/src/codex/_utils/_typing.py +++ b/src/codex/_utils/_typing.py @@ -26,6 +26,11 @@ def is_list_type(typ: type) -> bool: return (get_origin(typ) or typ) == list +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + def is_iterable_type(typ: type) -> bool: """If the given type is `typing.Iterable[T]`""" origin = get_origin(typ) or typ diff --git a/tests/utils.py b/tests/utils.py index d56fd573..091f4e4e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,7 @@ import inspect import traceback import contextlib -from typing import Any, TypeVar, Iterator, cast +from typing import Any, TypeVar, Iterator, Sequence, cast from datetime import date, datetime from typing_extensions import Literal, get_args, get_origin, assert_type @@ -15,6 +15,7 @@ is_list_type, is_union_type, extract_type_arg, + is_sequence_type, is_annotated_type, is_type_alias_type, ) @@ -71,6 +72,13 @@ def assert_matches_type( if is_list_type(type_): return _assert_list_type(type_, value) + if is_sequence_type(type_): + assert isinstance(value, Sequence) + inner_type = get_args(type_)[0] + for entry in value: # type: ignore + assert_type(inner_type, entry) # type: ignore + return + if origin == str: assert isinstance(value, str) elif origin == int: From 9890ae7db0d6c1e2cb8755d2272e497d37c0d4cf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 02:25:12 +0000 Subject: [PATCH 241/320] feat(types): replace List[str] with SequenceNotStr in params --- src/codex/_utils/_transform.py | 6 +++ src/codex/resources/projects/projects.py | 8 ++-- src/codex/resources/projects/query_logs.py | 42 +++++++++---------- src/codex/resources/tlm.py | 12 +++--- src/codex/types/project_validate_params.py | 7 ++-- .../query_log_list_by_group_params.py | 9 ++-- .../projects/query_log_list_groups_params.py | 7 ++-- .../types/projects/query_log_list_params.py | 7 ++-- src/codex/types/tlm_prompt_params.py | 8 ++-- src/codex/types/tlm_score_params.py | 8 ++-- 10 files changed, 64 insertions(+), 50 deletions(-) diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index b0cc20a7..f0bcefd4 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -16,6 +16,7 @@ lru_cache, is_mapping, is_iterable, + is_sequence, ) from .._files import is_base64_file_input from ._typing import ( @@ -24,6 +25,7 @@ extract_type_arg, is_iterable_type, is_required_type, + is_sequence_type, is_annotated_type, strip_annotated_type, ) @@ -184,6 +186,8 @@ def _transform_recursive( (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. @@ -346,6 +350,8 @@ async def _async_transform_recursive( (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 319097fd..f0ef92ab 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional +from typing import Dict, Iterable, Optional from typing_extensions import Literal import httpx @@ -23,7 +23,7 @@ project_invite_sme_params, project_retrieve_analytics_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven, SequenceNotStr from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from .query_logs import ( @@ -450,7 +450,7 @@ def validate( query: str, response: project_validate_params.Response, use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN, - constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, @@ -1028,7 +1028,7 @@ async def validate( query: str, response: project_validate_params.Response, use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN, - constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 6bbd7487..8fb37c0f 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -8,7 +8,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -104,13 +104,13 @@ def list( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] @@ -127,7 +127,7 @@ def list( ] ] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -248,19 +248,19 @@ def list_by_group( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - remediation_ids: List[str] | NotGiven = NOT_GIVEN, + remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, sort: Optional[ Literal[ "created_at", @@ -273,7 +273,7 @@ def list_by_group( ] ] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -359,21 +359,21 @@ def list_groups( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -550,13 +550,13 @@ def list( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] @@ -573,7 +573,7 @@ def list( ] ] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -696,19 +696,19 @@ async def list_by_group( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - remediation_ids: List[str] | NotGiven = NOT_GIVEN, + remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, sort: Optional[ Literal[ "created_at", @@ -721,7 +721,7 @@ async def list_by_group( ] ] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. @@ -807,21 +807,21 @@ def list_groups( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - failed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, needs_review: Optional[bool] | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[List[str]] | NotGiven = NOT_GIVEN, + passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[List[str]] | NotGiven = NOT_GIVEN, + tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # 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. diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index 5d66ec04..de652ef9 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal import httpx from ..types import tlm_score_params, tlm_prompt_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -49,7 +49,7 @@ def prompt( self, *, prompt: str, - constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, @@ -187,7 +187,7 @@ def score( *, prompt: str, response: str, - constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, @@ -351,7 +351,7 @@ async def prompt( self, *, prompt: str, - constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, @@ -489,7 +489,7 @@ async def score( *, prompt: str, response: str, - constrain_outputs: Optional[List[str]] | NotGiven = NOT_GIVEN, + constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 719ad3d3..56e2ae97 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -3,9 +3,10 @@ from __future__ import annotations import builtins -from typing import Dict, List, Union, Iterable, Optional +from typing import Dict, Union, Iterable, Optional from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo __all__ = [ @@ -68,7 +69,7 @@ class ProjectValidateParams(TypedDict, total=False): use_llm_matching: Optional[bool] - constrain_outputs: Optional[List[str]] + constrain_outputs: Optional[SequenceNotStr[str]] custom_eval_thresholds: Optional[Dict[str, float]] """Optional custom thresholds for specific evals. @@ -656,7 +657,7 @@ class Options(TypedDict, total=False): disable_trustworthiness: bool - log: List[str] + log: SequenceNotStr[str] max_tokens: int diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 5ca6d107..afdcb7f9 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -6,6 +6,7 @@ from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict +from ..._types import SequenceNotStr from ..._utils import PropertyInfo __all__ = ["QueryLogListByGroupParams"] @@ -21,7 +22,7 @@ class QueryLogListByGroupParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" - failed_evals: Optional[List[str]] + failed_evals: Optional[SequenceNotStr[str]] """Filter by evals that failed""" guardrailed: Optional[bool] @@ -39,7 +40,7 @@ class QueryLogListByGroupParams(TypedDict, total=False): order: Literal["asc", "desc"] - passed_evals: Optional[List[str]] + passed_evals: Optional[SequenceNotStr[str]] """Filter by evals that passed""" primary_eval_issue: Optional[ @@ -47,7 +48,7 @@ class QueryLogListByGroupParams(TypedDict, total=False): ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" - remediation_ids: List[str] + remediation_ids: SequenceNotStr[str] """List of groups to list child logs for""" sort: Optional[ @@ -62,7 +63,7 @@ class QueryLogListByGroupParams(TypedDict, total=False): ] ] - tool_call_names: Optional[List[str]] + tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" was_cache_hit: Optional[bool] diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index 6adefdf5..abb6a54a 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -6,6 +6,7 @@ from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict +from ..._types import SequenceNotStr from ..._utils import PropertyInfo __all__ = ["QueryLogListGroupsParams"] @@ -21,7 +22,7 @@ class QueryLogListGroupsParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" - failed_evals: Optional[List[str]] + failed_evals: Optional[SequenceNotStr[str]] """Filter by evals that failed""" guardrailed: Optional[bool] @@ -39,7 +40,7 @@ class QueryLogListGroupsParams(TypedDict, total=False): order: Literal["asc", "desc"] - passed_evals: Optional[List[str]] + passed_evals: Optional[SequenceNotStr[str]] """Filter by evals that passed""" primary_eval_issue: Optional[ @@ -49,7 +50,7 @@ class QueryLogListGroupsParams(TypedDict, total=False): sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] - tool_call_names: Optional[List[str]] + tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" was_cache_hit: Optional[bool] diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 2da646f8..20a209b6 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -6,6 +6,7 @@ from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict +from ..._types import SequenceNotStr from ..._utils import PropertyInfo __all__ = ["QueryLogListParams"] @@ -21,7 +22,7 @@ class QueryLogListParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" - failed_evals: Optional[List[str]] + failed_evals: Optional[SequenceNotStr[str]] """Filter by evals that failed""" guardrailed: Optional[bool] @@ -36,7 +37,7 @@ class QueryLogListParams(TypedDict, total=False): order: Literal["asc", "desc"] - passed_evals: Optional[List[str]] + passed_evals: Optional[SequenceNotStr[str]] """Filter by evals that passed""" primary_eval_issue: Optional[ @@ -56,7 +57,7 @@ class QueryLogListParams(TypedDict, total=False): ] ] - tool_call_names: Optional[List[str]] + tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" was_cache_hit: Optional[bool] diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py index 821c3811..6a2a9da3 100644 --- a/src/codex/types/tlm_prompt_params.py +++ b/src/codex/types/tlm_prompt_params.py @@ -2,16 +2,18 @@ from __future__ import annotations -from typing import List, Iterable, Optional +from typing import Iterable, Optional from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + __all__ = ["TlmPromptParams", "Options"] class TlmPromptParams(TypedDict, total=False): prompt: Required[str] - constrain_outputs: Optional[List[str]] + constrain_outputs: Optional[SequenceNotStr[str]] options: Optional[Options] """ @@ -119,7 +121,7 @@ class Options(TypedDict, total=False): disable_trustworthiness: bool - log: List[str] + log: SequenceNotStr[str] max_tokens: int diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py index d676a1d6..cef4f490 100644 --- a/src/codex/types/tlm_score_params.py +++ b/src/codex/types/tlm_score_params.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import List, Iterable, Optional +from typing import Iterable, Optional from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + __all__ = ["TlmScoreParams", "Options"] @@ -13,7 +15,7 @@ class TlmScoreParams(TypedDict, total=False): response: Required[str] - constrain_outputs: Optional[List[str]] + constrain_outputs: Optional[SequenceNotStr[str]] options: Optional[Options] """ @@ -121,7 +123,7 @@ class Options(TypedDict, total=False): disable_trustworthiness: bool - log: List[str] + log: SequenceNotStr[str] max_tokens: int From 9592a0d316c67d7727e6339c519fc29911e464a9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 02:29:39 +0000 Subject: [PATCH 242/320] feat: improve future compat with pydantic v3 --- src/codex/_base_client.py | 6 +- src/codex/_compat.py | 96 ++++++++--------- src/codex/_models.py | 80 +++++++------- src/codex/_utils/__init__.py | 10 +- src/codex/_utils/_compat.py | 45 ++++++++ src/codex/_utils/_datetime_parse.py | 136 ++++++++++++++++++++++++ src/codex/_utils/_transform.py | 6 +- src/codex/_utils/_typing.py | 2 +- src/codex/_utils/_utils.py | 1 - tests/test_models.py | 48 ++++----- tests/test_transform.py | 16 +-- tests/test_utils/test_datetime_parse.py | 110 +++++++++++++++++++ tests/utils.py | 8 +- 13 files changed, 432 insertions(+), 132 deletions(-) create mode 100644 src/codex/_utils/_compat.py create mode 100644 src/codex/_utils/_datetime_parse.py create mode 100644 tests/test_utils/test_datetime_parse.py diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 870a4729..e424fb76 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -59,7 +59,7 @@ ModelBuilderProtocol, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import PYDANTIC_V2, model_copy, model_dump +from ._compat import PYDANTIC_V1, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -232,7 +232,7 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model @@ -320,7 +320,7 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model diff --git a/src/codex/_compat.py b/src/codex/_compat.py index 92d9ee61..bdef67f0 100644 --- a/src/codex/_compat.py +++ b/src/codex/_compat.py @@ -12,14 +12,13 @@ _T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) -# --------------- Pydantic v2 compatibility --------------- +# --------------- Pydantic v2, v3 compatibility --------------- # Pyright incorrectly reports some of our functions as overriding a method when they don't # pyright: reportIncompatibleMethodOverride=false -PYDANTIC_V2 = pydantic.VERSION.startswith("2.") +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") -# v1 re-exports if TYPE_CHECKING: def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 @@ -44,90 +43,92 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 ... else: - if PYDANTIC_V2: - from pydantic.v1.typing import ( + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( get_args as get_args, is_union as is_union, get_origin as get_origin, is_typeddict as is_typeddict, is_literal_type as is_literal_type, ) - from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime else: - from pydantic.typing import ( + from ._utils import ( get_args as get_args, is_union as is_union, get_origin as get_origin, + parse_date as parse_date, is_typeddict as is_typeddict, + parse_datetime as parse_datetime, is_literal_type as is_literal_type, ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime # refactored config if TYPE_CHECKING: from pydantic import ConfigDict as ConfigDict else: - if PYDANTIC_V2: - from pydantic import ConfigDict - else: + if PYDANTIC_V1: # TODO: provide an error message here? ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict # renamed methods / properties def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(value) - else: + if PYDANTIC_V1: return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V2: - return field.is_required() - return field.required # type: ignore + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() def field_get_default(field: FieldInfo) -> Any: value = field.get_default() - if PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None + if PYDANTIC_V1: return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None return value def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V2: - return field.annotation - return field.outer_type_ # type: ignore + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V2: - return model.model_config - return model.__config__ # type: ignore + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V2: - return model.model_fields - return model.__fields__ # type: ignore + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: - if PYDANTIC_V2: - return model.model_copy(deep=deep) - return model.copy(deep=deep) # type: ignore + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V2: - return model.model_dump_json(indent=indent) - return model.json(indent=indent) # type: ignore + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) def model_dump( @@ -139,14 +140,14 @@ def model_dump( warnings: bool = True, mode: Literal["json", "python"] = "python", ) -> dict[str, Any]: - if PYDANTIC_V2 or hasattr(model, "model_dump"): + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( mode=mode, exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 - warnings=warnings if PYDANTIC_V2 else True, + warnings=True if PYDANTIC_V1 else warnings, ) return cast( "dict[str, Any]", @@ -159,9 +160,9 @@ def model_dump( def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(data) - return model.parse_obj(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) # generic models @@ -170,17 +171,16 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT: class GenericModel(pydantic.BaseModel): ... else: - if PYDANTIC_V2: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: # there no longer needs to be a distinction in v2 but # we still have to create our own subclass to avoid # inconsistent MRO ordering errors class GenericModel(pydantic.BaseModel): ... - else: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... - # cached properties if TYPE_CHECKING: diff --git a/src/codex/_models.py b/src/codex/_models.py index 92f7c10b..3a6017ef 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -50,7 +50,7 @@ strip_annotated_type, ) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -81,11 +81,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 @@ -95,6 +91,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, @@ -215,25 +215,25 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] if key not in model_fields: parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value - if PYDANTIC_V2: - _extra[key] = parsed - else: + if PYDANTIC_V1: _fields_set.add(key) 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 @@ -243,7 +243,7 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] # although not in practice model_construct = construct - if not PYDANTIC_V2: + if PYDANTIC_V1: # we define aliases for some of the new pydantic v2 methods so # that we can just document these methods without having to specify # a specific pydantic version as some users may not know which @@ -363,10 +363,10 @@ 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}") @@ -375,7 +375,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: - if not PYDANTIC_V2: + if PYDANTIC_V1: # TODO return None @@ -628,30 +628,30 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, for variant in get_args(union): variant = strip_annotated_type(variant) if is_basemodel_type(variant): - if PYDANTIC_V2: - field = _extract_field_schema_pv2(variant, discriminator_field_name) - if not field: + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field.get("serialization_alias") - - field_schema = field["schema"] + discriminator_alias = field_info.alias - if field_schema["type"] == "literal": - for entry in cast("LiteralSchema", field_schema)["expected"]: + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant else: - field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - if not field_info: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field_info.alias + discriminator_alias = field.get("serialization_alias") - if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): - for entry in get_args(annotation): + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: if isinstance(entry, str): mapping[entry] = variant @@ -714,7 +714,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)) @@ -782,12 +782,12 @@ class FinalRequestOptions(pydantic.BaseModel): 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): @@ -820,9 +820,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/codex/_utils/__init__.py b/src/codex/_utils/__init__.py index ca547ce5..dc64e29a 100644 --- a/src/codex/_utils/__init__.py +++ b/src/codex/_utils/__init__.py @@ -10,7 +10,6 @@ lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, - parse_date as parse_date, is_iterable as is_iterable, is_sequence as is_sequence, coerce_float as coerce_float, @@ -23,7 +22,6 @@ coerce_boolean as coerce_boolean, coerce_integer as coerce_integer, file_from_path as file_from_path, - parse_datetime as parse_datetime, strip_not_given as strip_not_given, deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, @@ -32,6 +30,13 @@ 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, @@ -56,3 +61,4 @@ function_has_argument as function_has_argument, assert_signatures_in_sync as assert_signatures_in_sync, ) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/codex/_utils/_compat.py b/src/codex/_utils/_compat.py new file mode 100644 index 00000000..dd703233 --- /dev/null +++ b/src/codex/_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 + + +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/codex/_utils/_datetime_parse.py b/src/codex/_utils/_datetime_parse.py new file mode 100644 index 00000000..7cb9d9e6 --- /dev/null +++ b/src/codex/_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/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index f0bcefd4..c19124f0 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -19,6 +19,7 @@ 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, @@ -29,7 +30,6 @@ is_annotated_type, strip_annotated_type, ) -from .._compat import get_origin, model_dump, is_typeddict _T = TypeVar("_T") @@ -169,6 +169,8 @@ def _transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation @@ -333,6 +335,8 @@ async def _async_transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py index 845cd6b2..193109f3 100644 --- a/src/codex/_utils/_typing.py +++ b/src/codex/_utils/_typing.py @@ -15,7 +15,7 @@ from ._utils import lru_cache from .._types import InheritsGeneric -from .._compat import is_union as _is_union +from ._compat import is_union as _is_union def is_annotated_type(typ: type) -> bool: diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py index ea3cf3f2..f0818595 100644 --- a/src/codex/_utils/_utils.py +++ b/src/codex/_utils/_utils.py @@ -22,7 +22,6 @@ import sniffio from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import parse_date as parse_date, parse_datetime as parse_datetime _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) diff --git a/tests/test_models.py b/tests/test_models.py index a9897029..61fe5946 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -8,7 +8,7 @@ from pydantic import Field from codex._utils import PropertyInfo -from codex._compat import PYDANTIC_V2, parse_obj, model_dump, model_json +from codex._compat import PYDANTIC_V1, parse_obj, model_dump, model_json from codex._models import BaseModel, construct_type @@ -294,12 +294,12 @@ class Model(BaseModel): assert cast(bool, m.foo) is True m = Model.construct(foo={"name": 3}) - if PYDANTIC_V2: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - else: + if PYDANTIC_V1: assert isinstance(m.foo, Submodel2) assert m.foo.name == "3" + else: + assert isinstance(m.foo, Submodel1) + assert m.foo.name == 3 # type: ignore def test_list_of_unions() -> None: @@ -426,10 +426,10 @@ class Model(BaseModel): expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - if PYDANTIC_V2: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - else: + if PYDANTIC_V1: expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + else: + expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' model = Model.construct(created_at="2019-12-27T18:11:19.117Z") assert model.created_at == expected @@ -531,7 +531,7 @@ class Model2(BaseModel): assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} assert m4.to_dict(mode="json") == {"created_at": time_str} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_dict(warnings=False) @@ -556,7 +556,7 @@ class Model(BaseModel): assert m3.model_dump() == {"foo": None} assert m3.model_dump(exclude_none=True) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) @@ -580,10 +580,10 @@ class Model(BaseModel): assert json.loads(m.to_json()) == {"FOO": "hello"} assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"} - if PYDANTIC_V2: - assert m.to_json(indent=None) == '{"FOO":"hello"}' - else: + if PYDANTIC_V1: assert m.to_json(indent=None) == '{"FOO": "hello"}' + else: + assert m.to_json(indent=None) == '{"FOO":"hello"}' m2 = Model() assert json.loads(m2.to_json()) == {} @@ -595,7 +595,7 @@ class Model(BaseModel): assert json.loads(m3.to_json()) == {"FOO": None} assert json.loads(m3.to_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_json(warnings=False) @@ -622,7 +622,7 @@ class Model(BaseModel): assert json.loads(m3.model_dump_json()) == {"foo": None} assert json.loads(m3.model_dump_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump_json(round_trip=True) @@ -679,12 +679,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_unknown_variant() -> None: @@ -768,12 +768,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.foo_type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: @@ -833,7 +833,7 @@ class B(BaseModel): assert UnionType.__discriminator__ is discriminator -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: Alias = TypeAliasType("Alias", str) # pyright: ignore @@ -849,7 +849,7 @@ class Model(BaseModel): assert m.union == "bar" -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_field_named_cls() -> None: class Model(BaseModel): cls: str @@ -936,7 +936,7 @@ class Type2(BaseModel): assert isinstance(model.value, InnerType2) -@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now") +@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 diff --git a/tests/test_transform.py b/tests/test_transform.py index 527845a0..4067f583 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -15,7 +15,7 @@ parse_datetime, async_transform as _async_transform, ) -from codex._compat import PYDANTIC_V2 +from codex._compat import PYDANTIC_V1 from codex._models import BaseModel _T = TypeVar("_T") @@ -189,7 +189,7 @@ class DateModel(BaseModel): @pytest.mark.asyncio async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - tz = "Z" if PYDANTIC_V2 else "+00:00" + tz = "+00:00" if PYDANTIC_V1 else "Z" assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] @@ -297,11 +297,11 @@ async def test_pydantic_unknown_field(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_types(use_async: bool) -> None: model = MyModel.construct(foo=True) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": True} @@ -309,11 +309,11 @@ async def test_pydantic_mismatched_types(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_object_type(use_async: bool) -> None: model = MyModel.construct(foo=MyModel.construct(hello="world")) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": {"hello": "world"}} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py new file mode 100644 index 00000000..f7007b79 --- /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 codex._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/utils.py b/tests/utils.py index 091f4e4e..f53a1fdb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -19,7 +19,7 @@ is_annotated_type, is_type_alias_type, ) -from codex._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from codex._compat import PYDANTIC_V1, field_outer_type, get_model_fields from codex._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) @@ -28,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), From dd66a5e67c341e9d9dede9694b09f202bb558c82 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 20:17:22 +0000 Subject: [PATCH 243/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 52 ++----------------- .../query_log_list_by_group_params.py | 12 +---- .../types/projects/query_log_list_params.py | 12 +---- .../api_resources/projects/test_query_logs.py | 8 +-- 5 files changed, 11 insertions(+), 75 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1be05f48..1b83cc3e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: b260cce23d53dba37b982654e7928405 +openapi_spec_hash: 3d3361adb6a836f42a49850b5aea1df5 config_hash: bed87752f4056d0c4bf2ddf856307800 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 8fb37c0f..98954bf5 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -115,18 +115,7 @@ def list( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[ - Literal[ - "created_at", - "primary_eval_issue_score", - "score_trustworthiness", - "score_context_sufficiency", - "score_response_helpfulness", - "score_query_ease", - "score_response_groundedness", - ] - ] - | NotGiven = NOT_GIVEN, + sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -261,18 +250,7 @@ def list_by_group( ] | NotGiven = NOT_GIVEN, remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, - sort: Optional[ - Literal[ - "created_at", - "primary_eval_issue_score", - "score_trustworthiness", - "score_context_sufficiency", - "score_response_helpfulness", - "score_query_ease", - "score_response_groundedness", - ] - ] - | NotGiven = NOT_GIVEN, + sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -561,18 +539,7 @@ def list( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[ - Literal[ - "created_at", - "primary_eval_issue_score", - "score_trustworthiness", - "score_context_sufficiency", - "score_response_helpfulness", - "score_query_ease", - "score_response_groundedness", - ] - ] - | NotGiven = NOT_GIVEN, + sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -709,18 +676,7 @@ async def list_by_group( ] | NotGiven = NOT_GIVEN, remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, - sort: Optional[ - Literal[ - "created_at", - "primary_eval_issue_score", - "score_trustworthiness", - "score_context_sufficiency", - "score_response_helpfulness", - "score_query_ease", - "score_response_groundedness", - ] - ] - | NotGiven = NOT_GIVEN, + sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index afdcb7f9..7eb24103 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -51,17 +51,7 @@ class QueryLogListByGroupParams(TypedDict, total=False): remediation_ids: SequenceNotStr[str] """List of groups to list child logs for""" - sort: Optional[ - Literal[ - "created_at", - "primary_eval_issue_score", - "score_trustworthiness", - "score_context_sufficiency", - "score_response_helpfulness", - "score_query_ease", - "score_response_groundedness", - ] - ] + sort: Optional[str] tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 20a209b6..f0037632 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -45,17 +45,7 @@ class QueryLogListParams(TypedDict, total=False): ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" - sort: Optional[ - Literal[ - "created_at", - "primary_eval_issue_score", - "score_trustworthiness", - "score_context_sufficiency", - "score_response_helpfulness", - "score_query_ease", - "score_response_groundedness", - ] - ] + sort: Optional[str] tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 05d3f46e..385d44a3 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -107,7 +107,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], - sort="created_at", + sort="sort", tool_call_names=["string"], was_cache_hit=True, ) @@ -230,7 +230,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - sort="created_at", + sort="sort", tool_call_names=["string"], was_cache_hit=True, ) @@ -469,7 +469,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], - sort="created_at", + sort="sort", tool_call_names=["string"], was_cache_hit=True, ) @@ -592,7 +592,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - sort="created_at", + sort="sort", tool_call_names=["string"], was_cache_hit=True, ) From 56cfcb1ac3ac45413f4c3ec470a84c70349ceb6e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 02:43:04 +0000 Subject: [PATCH 244/320] chore(internal): move mypy configurations to `pyproject.toml` file --- mypy.ini | 50 ------------------------------------------------ pyproject.toml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 50 deletions(-) delete mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 92f9243b..00000000 --- a/mypy.ini +++ /dev/null @@ -1,50 +0,0 @@ -[mypy] -pretty = True -show_error_codes = True - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -# -# We also exclude our `tests` as mypy doesn't always infer -# types correctly and Pyright will still catch any type errors. -exclude = ^(src/codex/_files\.py|_dev/.*\.py|tests/.*)$ - -strict_equality = True -implicit_reexport = True -check_untyped_defs = True -no_implicit_optional = True - -warn_return_any = True -warn_unreachable = True -warn_unused_configs = True - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = False -warn_redundant_casts = False - -disallow_any_generics = True -disallow_untyped_defs = True -disallow_untyped_calls = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -cache_fine_grained = True - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = func-returns-value,overload-cannot-match - -# https://github.com/python/mypy/issues/12162 -[mypy.overrides] -module = "black.files.*" -ignore_errors = true -ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index 86e4b99e..4a9d1309 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,6 +157,58 @@ 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/codex/_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" From f5d09a2728469b5cec57ef45f56dd3e5f60ac972 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:17:26 +0000 Subject: [PATCH 245/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 1b83cc3e..caeeb0fd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 3d3361adb6a836f42a49850b5aea1df5 +openapi_spec_hash: 12260ab88069ff15d254606e041debfb config_hash: bed87752f4056d0c4bf2ddf856307800 From e60d0a7b5892746640db0c637277265280cbbcc4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:21:36 +0000 Subject: [PATCH 246/320] feat(api): manual updates --- .stats.yml | 4 +- api.md | 2 + src/codex/resources/projects/query_logs.py | 90 ++++++++++++++ src/codex/types/projects/__init__.py | 2 + .../query_log_update_metadata_params.py | 13 ++ .../query_log_update_metadata_response.py | 17 +++ .../api_resources/projects/test_query_logs.py | 115 ++++++++++++++++++ 7 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 src/codex/types/projects/query_log_update_metadata_params.py create mode 100644 src/codex/types/projects/query_log_update_metadata_response.py diff --git a/.stats.yml b/.stats.yml index caeeb0fd..386ca3e4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 55 +configured_endpoints: 56 openapi_spec_hash: 12260ab88069ff15d254606e041debfb -config_hash: bed87752f4056d0c4bf2ddf856307800 +config_hash: 6c3ad84d97bf1d0989ad2ec0cae64078 diff --git a/api.md b/api.md index 693ee75f..a78c4fc4 100644 --- a/api.md +++ b/api.md @@ -206,6 +206,7 @@ from codex.types.projects import ( QueryLogListByGroupResponse, QueryLogListGroupsResponse, QueryLogStartRemediationResponse, + QueryLogUpdateMetadataResponse, ) ``` @@ -217,6 +218,7 @@ Methods: - client.projects.query_logs.list_by_group(project_id, \*\*params) -> QueryLogListByGroupResponse - client.projects.query_logs.list_groups(project_id, \*\*params) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse] - client.projects.query_logs.start_remediation(query_log_id, \*, project_id) -> QueryLogStartRemediationResponse +- client.projects.query_logs.update_metadata(query_log_id, \*, project_id, \*\*params) -> QueryLogUpdateMetadataResponse ## Remediations diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 98954bf5..8c939f97 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -29,12 +29,14 @@ query_log_list_params, query_log_list_groups_params, query_log_list_by_group_params, + query_log_update_metadata_params, query_log_add_user_feedback_params, ) from ...types.projects.query_log_list_response import QueryLogListResponse from ...types.projects.query_log_retrieve_response import QueryLogRetrieveResponse from ...types.projects.query_log_list_groups_response import QueryLogListGroupsResponse from ...types.projects.query_log_list_by_group_response import QueryLogListByGroupResponse +from ...types.projects.query_log_update_metadata_response import QueryLogUpdateMetadataResponse from ...types.projects.query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse from ...types.projects.query_log_start_remediation_response import QueryLogStartRemediationResponse @@ -464,6 +466,44 @@ def start_remediation( cast_to=QueryLogStartRemediationResponse, ) + def update_metadata( + self, + query_log_id: str, + *, + project_id: str, + body: object, + # 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, + ) -> QueryLogUpdateMetadataResponse: + """ + Update Metadata Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return self._put( + f"/api/projects/{project_id}/query_logs/{query_log_id}/metadata", + body=maybe_transform(body, query_log_update_metadata_params.QueryLogUpdateMetadataParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogUpdateMetadataResponse, + ) + class AsyncQueryLogsResource(AsyncAPIResource): @cached_property @@ -890,6 +930,44 @@ async def start_remediation( cast_to=QueryLogStartRemediationResponse, ) + async def update_metadata( + self, + query_log_id: str, + *, + project_id: str, + body: object, + # 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, + ) -> QueryLogUpdateMetadataResponse: + """ + Update Metadata Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not query_log_id: + raise ValueError(f"Expected a non-empty value for `query_log_id` but received {query_log_id!r}") + return await self._put( + f"/api/projects/{project_id}/query_logs/{query_log_id}/metadata", + body=await async_maybe_transform(body, query_log_update_metadata_params.QueryLogUpdateMetadataParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=QueryLogUpdateMetadataResponse, + ) + class QueryLogsResourceWithRawResponse: def __init__(self, query_logs: QueryLogsResource) -> None: @@ -913,6 +991,9 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.start_remediation = to_raw_response_wrapper( query_logs.start_remediation, ) + self.update_metadata = to_raw_response_wrapper( + query_logs.update_metadata, + ) class AsyncQueryLogsResourceWithRawResponse: @@ -937,6 +1018,9 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.start_remediation = async_to_raw_response_wrapper( query_logs.start_remediation, ) + self.update_metadata = async_to_raw_response_wrapper( + query_logs.update_metadata, + ) class QueryLogsResourceWithStreamingResponse: @@ -961,6 +1045,9 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.start_remediation = to_streamed_response_wrapper( query_logs.start_remediation, ) + self.update_metadata = to_streamed_response_wrapper( + query_logs.update_metadata, + ) class AsyncQueryLogsResourceWithStreamingResponse: @@ -985,3 +1072,6 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.start_remediation = async_to_streamed_response_wrapper( query_logs.start_remediation, ) + self.update_metadata = async_to_streamed_response_wrapper( + query_logs.update_metadata, + ) diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index b90cbd89..c8a6b9a2 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -26,8 +26,10 @@ from .query_log_list_groups_response import QueryLogListGroupsResponse as QueryLogListGroupsResponse from .remediation_edit_answer_params import RemediationEditAnswerParams as RemediationEditAnswerParams from .query_log_list_by_group_response import QueryLogListByGroupResponse as QueryLogListByGroupResponse +from .query_log_update_metadata_params import QueryLogUpdateMetadataParams as QueryLogUpdateMetadataParams from .remediation_edit_answer_response import RemediationEditAnswerResponse as RemediationEditAnswerResponse from .query_log_add_user_feedback_params import QueryLogAddUserFeedbackParams as QueryLogAddUserFeedbackParams +from .query_log_update_metadata_response import QueryLogUpdateMetadataResponse as QueryLogUpdateMetadataResponse from .query_log_add_user_feedback_response import QueryLogAddUserFeedbackResponse as QueryLogAddUserFeedbackResponse from .query_log_start_remediation_response import QueryLogStartRemediationResponse as QueryLogStartRemediationResponse from .remediation_edit_draft_answer_params import RemediationEditDraftAnswerParams as RemediationEditDraftAnswerParams diff --git a/src/codex/types/projects/query_log_update_metadata_params.py b/src/codex/types/projects/query_log_update_metadata_params.py new file mode 100644 index 00000000..6f58c5d9 --- /dev/null +++ b/src/codex/types/projects/query_log_update_metadata_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["QueryLogUpdateMetadataParams"] + + +class QueryLogUpdateMetadataParams(TypedDict, total=False): + project_id: Required[str] + + body: Required[object] diff --git a/src/codex/types/projects/query_log_update_metadata_response.py b/src/codex/types/projects/query_log_update_metadata_response.py new file mode 100644 index 00000000..99375429 --- /dev/null +++ b/src/codex/types/projects/query_log_update_metadata_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel + +__all__ = ["QueryLogUpdateMetadataResponse"] + + +class QueryLogUpdateMetadataResponse(BaseModel): + id: str + + custom_metadata: Optional[object] = None + """Arbitrary metadata supplied by the user/system""" + + custom_metadata_keys: Optional[List[str]] = None + """Keys of the custom metadata""" diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 385d44a3..5f9d1e92 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -21,6 +21,7 @@ QueryLogRetrieveResponse, QueryLogListGroupsResponse, QueryLogListByGroupResponse, + QueryLogUpdateMetadataResponse, QueryLogAddUserFeedbackResponse, QueryLogStartRemediationResponse, ) @@ -387,6 +388,63 @@ def test_path_params_start_remediation(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update_metadata(self, client: Codex) -> None: + query_log = client.projects.query_logs.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) + assert_matches_type(QueryLogUpdateMetadataResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_update_metadata(self, client: Codex) -> None: + response = client.projects.query_logs.with_raw_response.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = response.parse() + assert_matches_type(QueryLogUpdateMetadataResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_update_metadata(self, client: Codex) -> None: + with client.projects.query_logs.with_streaming_response.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = response.parse() + assert_matches_type(QueryLogUpdateMetadataResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_update_metadata(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + body={}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + client.projects.query_logs.with_raw_response.update_metadata( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) + class TestAsyncQueryLogs: parametrize = pytest.mark.parametrize( @@ -748,3 +806,60 @@ async def test_path_params_start_remediation(self, async_client: AsyncCodex) -> query_log_id="", project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update_metadata(self, async_client: AsyncCodex) -> None: + query_log = await async_client.projects.query_logs.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) + assert_matches_type(QueryLogUpdateMetadataResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_update_metadata(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.query_logs.with_raw_response.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + query_log = await response.parse() + assert_matches_type(QueryLogUpdateMetadataResponse, query_log, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_update_metadata(self, async_client: AsyncCodex) -> None: + async with async_client.projects.query_logs.with_streaming_response.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + query_log = await response.parse() + assert_matches_type(QueryLogUpdateMetadataResponse, query_log, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_update_metadata(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.update_metadata( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + body={}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.update_metadata( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + body={}, + ) From 534e324033dcb9f5cf1c4edc834e135d00427036 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:25:15 +0000 Subject: [PATCH 247/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3188cedb..315f7d30 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.25" + ".": "0.1.0-alpha.26" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4a9d1309..484c1234 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.25" +version = "0.1.0-alpha.26" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 656ce65e..1e7d5f15 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.25" # x-release-please-version +__version__ = "0.1.0-alpha.26" # x-release-please-version From b6e5e1fbe438d6def9370d6c965e936c810e8eb2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Sep 2025 02:48:14 +0000 Subject: [PATCH 248/320] chore(tests): simplify `get_platform` test `nest_asyncio` is archived and broken on some platforms so it's not worth keeping in our test suite. --- pyproject.toml | 1 - requirements-dev.lock | 1 - tests/test_client.py | 53 +++++-------------------------------------- 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 484c1234..70e7e284 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0", "pytest-xdist>=3.6.1", ] diff --git a/requirements-dev.lock b/requirements-dev.lock index 7999ff41..ff0a2412 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -75,7 +75,6 @@ multidict==6.4.4 mypy==1.14.1 mypy-extensions==1.0.0 # via mypy -nest-asyncio==1.6.0 nodeenv==1.8.0 # via pyright nox==2023.4.22 diff --git a/tests/test_client.py b/tests/test_client.py index cdc717fd..438d827a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,13 +6,10 @@ import os import sys import json -import time import asyncio import inspect -import subprocess import tracemalloc from typing import Any, Union, cast -from textwrap import dedent from unittest import mock from typing_extensions import Literal @@ -23,14 +20,17 @@ from codex import Codex, AsyncCodex, APIResponseValidationError from codex._types import Omit +from codex._utils import asyncify from codex._models import BaseModel, FinalRequestOptions from codex._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError from codex._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + OtherPlatform, DefaultHttpxClient, DefaultAsyncHttpxClient, + get_platform, make_request_options, ) @@ -1671,50 +1671,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" - def test_get_platform(self) -> None: - # A previous implementation of asyncify could leave threads unterminated when - # used with nest_asyncio. - # - # Since nest_asyncio.apply() is global and cannot be un-applied, this - # test is run in a separate process to avoid affecting other tests. - test_code = dedent(""" - import asyncio - import nest_asyncio - import threading - - from codex._utils import asyncify - from codex._base_client import get_platform - - async def test_main() -> None: - result = await asyncify(get_platform)() - print(result) - for thread in threading.enumerate(): - print(thread.name) - - nest_asyncio.apply() - asyncio.run(test_main()) - """) - with subprocess.Popen( - [sys.executable, "-c", test_code], - text=True, - ) as process: - timeout = 10 # seconds - - start_time = time.monotonic() - while True: - return_code = process.poll() - if return_code is not None: - if return_code != 0: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - - # success - break - - if time.monotonic() - start_time > timeout: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") - - time.sleep(0.1) + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly From b80b87ffcda32b2d63f74d25c08791e01cf9073a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:17:36 +0000 Subject: [PATCH 249/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 74 ++++++++++++++++++- .../query_log_list_by_group_params.py | 11 +++ .../projects/query_log_list_groups_params.py | 14 +++- .../types/projects/query_log_list_params.py | 11 +++ .../api_resources/projects/test_query_logs.py | 8 +- 6 files changed, 110 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index 386ca3e4..17038a12 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 12260ab88069ff15d254606e041debfb +openapi_spec_hash: 3edbb8139be919b07598ed85ebea9878 config_hash: 6c3ad84d97bf1d0989ad2ec0cae64078 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 8c939f97..de81c1fb 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -147,6 +147,17 @@ def list( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + sort: Field or score to sort by. + + Available fields: 'created_at', 'primary_eval_issue_score'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + tool_call_names: Filter by names of tools called in the assistant response was_cache_hit: Filter by cache hit status @@ -286,6 +297,17 @@ def list_by_group( remediation_ids: List of groups to list child logs for + sort: Field or score to sort by. + + Available fields: 'created_at', 'primary_eval_issue_score'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + tool_call_names: Filter by names of tools called in the assistant response was_cache_hit: Filter by cache hit status @@ -351,8 +373,7 @@ def list_groups( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] - | NotGiven = NOT_GIVEN, + sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -384,6 +405,18 @@ def list_groups( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + sort: Field or score to sort by. + + Available fields: 'created_at', 'custom_rank', 'impact_score', + 'primary_eval_issue_score', 'total_count'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + tool_call_names: Filter by names of tools called in the assistant response was_cache_hit: Filter by cache hit status @@ -609,6 +642,17 @@ def list( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + sort: Field or score to sort by. + + Available fields: 'created_at', 'primary_eval_issue_score'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + tool_call_names: Filter by names of tools called in the assistant response was_cache_hit: Filter by cache hit status @@ -750,6 +794,17 @@ async def list_by_group( remediation_ids: List of groups to list child logs for + sort: Field or score to sort by. + + Available fields: 'created_at', 'primary_eval_issue_score'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + tool_call_names: Filter by names of tools called in the assistant response was_cache_hit: Filter by cache hit status @@ -815,8 +870,7 @@ def list_groups( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] - | NotGiven = NOT_GIVEN, + sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -848,6 +902,18 @@ def list_groups( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + sort: Field or score to sort by. + + Available fields: 'created_at', 'custom_rank', 'impact_score', + 'primary_eval_issue_score', 'total_count'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + tool_call_names: Filter by names of tools called in the assistant response was_cache_hit: Filter by cache hit status diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 7eb24103..bb433955 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -52,6 +52,17 @@ class QueryLogListByGroupParams(TypedDict, total=False): """List of groups to list child logs for""" sort: Optional[str] + """Field or score to sort by. + + Available fields: 'created_at', 'primary_eval_issue_score'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + """ tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index abb6a54a..103930bb 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -48,7 +48,19 @@ class QueryLogListGroupsParams(TypedDict, total=False): ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" - sort: Optional[Literal["created_at", "primary_eval_issue_score", "total_count", "custom_rank", "impact_score"]] + sort: Optional[str] + """Field or score to sort by. + + Available fields: 'created_at', 'custom_rank', 'impact_score', + 'primary_eval_issue_score', 'total_count'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + """ tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index f0037632..0ea52aeb 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -46,6 +46,17 @@ class QueryLogListParams(TypedDict, total=False): """Filter logs that have ANY of these primary evaluation issues (OR operation)""" sort: Optional[str] + """Field or score to sort by. + + Available fields: 'created_at', 'primary_eval_issue_score'. + + For eval scores, use '.eval.' prefix followed by the eval name. + + Default eval scores: '.eval.trustworthiness', '.eval.context_sufficiency', + '.eval.response_helpfulness', '.eval.query_ease', '.eval.response_groundedness'. + + Custom eval scores: '.eval.custom_eval_1', '.eval.custom_eval_2', etc. + """ tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 5f9d1e92..ab8dcc66 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -108,7 +108,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], - sort="sort", + sort="created_at", tool_call_names=["string"], was_cache_hit=True, ) @@ -231,7 +231,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - sort="sort", + sort="created_at", tool_call_names=["string"], was_cache_hit=True, ) @@ -527,7 +527,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], - sort="sort", + sort="created_at", tool_call_names=["string"], was_cache_hit=True, ) @@ -650,7 +650,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - sort="sort", + sort="created_at", tool_call_names=["string"], was_cache_hit=True, ) From 49eb53d010946098e2e41a5ef720004ce204b6dc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:17:30 +0000 Subject: [PATCH 250/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/projects/query_log_list_by_group_response.py | 3 +++ src/codex/types/projects/query_log_list_groups_response.py | 3 +++ src/codex/types/projects/query_log_retrieve_response.py | 3 +++ .../types/projects/query_log_start_remediation_response.py | 3 +++ src/codex/types/projects/remediation_create_response.py | 3 +++ src/codex/types/projects/remediation_edit_answer_response.py | 3 +++ .../types/projects/remediation_edit_draft_answer_response.py | 3 +++ .../projects/remediation_get_resolved_logs_count_response.py | 3 +++ src/codex/types/projects/remediation_list_response.py | 3 +++ src/codex/types/projects/remediation_pause_response.py | 3 +++ src/codex/types/projects/remediation_publish_response.py | 3 +++ src/codex/types/projects/remediation_retrieve_response.py | 3 +++ src/codex/types/projects/remediation_unpause_response.py | 3 +++ 14 files changed, 40 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 17038a12..22e48c6c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 3edbb8139be919b07598ed85ebea9878 +openapi_spec_hash: 50e871c310076ede174715f6e76f673c config_hash: 6c3ad84d97bf1d0989ad2ec0cae64078 diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index fc33cdeb..17500b96 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -405,6 +405,9 @@ class QueryLogsByGroupQueryLog(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" + messages: Optional[List[QueryLogsByGroupQueryLogMessage]] = None """Message history to provide conversation context for the query. diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 7dbf1929..f99b4cdb 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -401,6 +401,9 @@ class QueryLogListGroupsResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" + messages: Optional[List[Message]] = None """Message history to provide conversation context for the query. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index b9be8d6d..92583090 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -393,6 +393,9 @@ class QueryLogRetrieveResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" + messages: Optional[List[Message]] = None """Message history to provide conversation context for the query. diff --git a/src/codex/types/projects/query_log_start_remediation_response.py b/src/codex/types/projects/query_log_start_remediation_response.py index ee7f0c72..73029a67 100644 --- a/src/codex/types/projects/query_log_start_remediation_response.py +++ b/src/codex/types/projects/query_log_start_remediation_response.py @@ -33,3 +33,6 @@ class QueryLogStartRemediationResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_create_response.py b/src/codex/types/projects/remediation_create_response.py index 9b8a8775..560a4814 100644 --- a/src/codex/types/projects/remediation_create_response.py +++ b/src/codex/types/projects/remediation_create_response.py @@ -33,3 +33,6 @@ class RemediationCreateResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_edit_answer_response.py b/src/codex/types/projects/remediation_edit_answer_response.py index 1d43c082..576d5065 100644 --- a/src/codex/types/projects/remediation_edit_answer_response.py +++ b/src/codex/types/projects/remediation_edit_answer_response.py @@ -33,3 +33,6 @@ class RemediationEditAnswerResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_edit_draft_answer_response.py b/src/codex/types/projects/remediation_edit_draft_answer_response.py index 80f80c07..bec3b4d6 100644 --- a/src/codex/types/projects/remediation_edit_draft_answer_response.py +++ b/src/codex/types/projects/remediation_edit_draft_answer_response.py @@ -33,3 +33,6 @@ class RemediationEditDraftAnswerResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py index 9222eb9a..3c7742c2 100644 --- a/src/codex/types/projects/remediation_get_resolved_logs_count_response.py +++ b/src/codex/types/projects/remediation_get_resolved_logs_count_response.py @@ -35,3 +35,6 @@ class RemediationGetResolvedLogsCountResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_list_response.py b/src/codex/types/projects/remediation_list_response.py index 83410539..300bacff 100644 --- a/src/codex/types/projects/remediation_list_response.py +++ b/src/codex/types/projects/remediation_list_response.py @@ -35,3 +35,6 @@ class RemediationListResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_pause_response.py b/src/codex/types/projects/remediation_pause_response.py index 97e1ac58..3919112f 100644 --- a/src/codex/types/projects/remediation_pause_response.py +++ b/src/codex/types/projects/remediation_pause_response.py @@ -33,3 +33,6 @@ class RemediationPauseResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_publish_response.py b/src/codex/types/projects/remediation_publish_response.py index b43c8b98..a76eefc9 100644 --- a/src/codex/types/projects/remediation_publish_response.py +++ b/src/codex/types/projects/remediation_publish_response.py @@ -33,3 +33,6 @@ class RemediationPublishResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_retrieve_response.py b/src/codex/types/projects/remediation_retrieve_response.py index 69a327e0..43f269d8 100644 --- a/src/codex/types/projects/remediation_retrieve_response.py +++ b/src/codex/types/projects/remediation_retrieve_response.py @@ -33,3 +33,6 @@ class RemediationRetrieveResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_unpause_response.py b/src/codex/types/projects/remediation_unpause_response.py index c3ce44fd..e8731c33 100644 --- a/src/codex/types/projects/remediation_unpause_response.py +++ b/src/codex/types/projects/remediation_unpause_response.py @@ -33,3 +33,6 @@ class RemediationUnpauseResponse(BaseModel): answer: Optional[str] = None draft_answer: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for remediations.""" From b93bc9f70398f5c019f2b4d9a87581ce3d624a99 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:17:44 +0000 Subject: [PATCH 251/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 24 +++++++++++++++++++ src/codex/types/project_validate_response.py | 3 +++ .../query_log_list_by_group_params.py | 3 +++ .../query_log_list_by_group_response.py | 15 ++++++++++++ .../projects/query_log_list_groups_params.py | 3 +++ .../query_log_list_groups_response.py | 15 ++++++++++++ .../types/projects/query_log_list_params.py | 3 +++ .../types/projects/query_log_list_response.py | 15 ++++++++++++ .../projects/query_log_retrieve_response.py | 15 ++++++++++++ ...remediation_list_resolved_logs_response.py | 15 ++++++++++++ .../api_resources/projects/test_query_logs.py | 6 +++++ 12 files changed, 118 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 22e48c6c..f4ab1b18 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 50e871c310076ede174715f6e76f673c +openapi_spec_hash: b752c79a72b1b72b4b5aea40e27c16c5 config_hash: 6c3ad84d97bf1d0989ad2ec0cae64078 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index de81c1fb..2dfe62ea 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -106,6 +106,7 @@ def list( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, @@ -137,6 +138,8 @@ def list( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + expert_review_status: Filter by expert review status + failed_evals: Filter by evals that failed guardrailed: Filter by guardrailed status @@ -185,6 +188,7 @@ def list( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "expert_review_status": expert_review_status, "failed_evals": failed_evals, "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, @@ -250,6 +254,7 @@ def list_by_group( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, @@ -283,6 +288,8 @@ def list_by_group( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + expert_review_status: Filter by expert review status + failed_evals: Filter by evals that failed guardrailed: Filter by guardrailed status @@ -334,6 +341,7 @@ def list_by_group( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "expert_review_status": expert_review_status, "failed_evals": failed_evals, "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, @@ -361,6 +369,7 @@ def list_groups( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, @@ -393,6 +402,8 @@ def list_groups( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + expert_review_status: Filter by expert review status + failed_evals: Filter by evals that failed guardrailed: Filter by guardrailed status @@ -444,6 +455,7 @@ def list_groups( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "expert_review_status": expert_review_status, "failed_evals": failed_evals, "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, @@ -601,6 +613,7 @@ def list( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, @@ -632,6 +645,8 @@ def list( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + expert_review_status: Filter by expert review status + failed_evals: Filter by evals that failed guardrailed: Filter by guardrailed status @@ -680,6 +695,7 @@ def list( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "expert_review_status": expert_review_status, "failed_evals": failed_evals, "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, @@ -747,6 +763,7 @@ async def list_by_group( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, @@ -780,6 +797,8 @@ async def list_by_group( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + expert_review_status: Filter by expert review status + failed_evals: Filter by evals that failed guardrailed: Filter by guardrailed status @@ -831,6 +850,7 @@ async def list_by_group( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "expert_review_status": expert_review_status, "failed_evals": failed_evals, "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, @@ -858,6 +878,7 @@ def list_groups( created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, + expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, @@ -890,6 +911,8 @@ def list_groups( custom_metadata: Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"} + expert_review_status: Filter by expert review status + failed_evals: Filter by evals that failed guardrailed: Filter by guardrailed status @@ -941,6 +964,7 @@ def list_groups( "created_at_end": created_at_end, "created_at_start": created_at_start, "custom_metadata": custom_metadata, + "expert_review_status": expert_review_status, "failed_evals": failed_evals, "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index 003b676c..e56d3bca 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -52,6 +52,9 @@ class ProjectValidateResponse(BaseModel): Codex Project, or None otherwise. """ + expert_review_guardrail_explanation: Optional[str] = None + """Explanation from a similar bad query log that caused this to be guardrailed""" + is_bad_response: bool """True if the response is flagged as potentially bad, False otherwise. diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index bb433955..73d18445 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -22,6 +22,9 @@ class QueryLogListByGroupParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + expert_review_status: Optional[Literal["good", "bad"]] + """Filter by expert review status""" + failed_evals: Optional[SequenceNotStr[str]] """Filter by evals that failed""" diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 17500b96..5cc3ac96 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -399,6 +399,18 @@ class QueryLogsByGroupQueryLog(BaseModel): Used to log tool calls in the query log. """ + expert_review_created_at: Optional[datetime] = None + """When the expert review was created""" + + expert_review_created_by_user_id: Optional[str] = None + """ID of the user who created the expert review""" + + expert_review_explanation: Optional[str] = None + """Expert explanation when marked as bad""" + + expert_review_status: Optional[Literal["good", "bad"]] = None + """Expert review status: 'good' or 'bad'""" + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -428,6 +440,9 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + similar_query_log_guardrail_explanation: Optional[str] = None + """Explanation from a similar bad query log that caused this to be guardrailed""" + tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index 103930bb..849a73ae 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -22,6 +22,9 @@ class QueryLogListGroupsParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + expert_review_status: Optional[Literal["good", "bad"]] + """Filter by expert review status""" + failed_evals: Optional[SequenceNotStr[str]] """Filter by evals that failed""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index f99b4cdb..11c24b21 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -395,6 +395,18 @@ class QueryLogListGroupsResponse(BaseModel): Used to log tool calls in the query log. """ + expert_review_created_at: Optional[datetime] = None + """When the expert review was created""" + + expert_review_created_by_user_id: Optional[str] = None + """ID of the user who created the expert review""" + + expert_review_explanation: Optional[str] = None + """Expert explanation when marked as bad""" + + expert_review_status: Optional[Literal["good", "bad"]] = None + """Expert review status: 'good' or 'bad'""" + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -424,6 +436,9 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + similar_query_log_guardrail_explanation: Optional[str] = None + """Explanation from a similar bad query log that caused this to be guardrailed""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 0ea52aeb..2d717ae1 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -22,6 +22,9 @@ class QueryLogListParams(TypedDict, total=False): custom_metadata: Optional[str] """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + expert_review_status: Optional[Literal["good", "bad"]] + """Filter by expert review status""" + failed_evals: Optional[SequenceNotStr[str]] """Filter by evals that failed""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index b56d43d3..098ac817 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -383,6 +383,18 @@ class QueryLogListResponse(BaseModel): Used to log tool calls in the query log. """ + expert_review_created_at: Optional[datetime] = None + """When the expert review was created""" + + expert_review_created_by_user_id: Optional[str] = None + """ID of the user who created the expert review""" + + expert_review_explanation: Optional[str] = None + """Expert explanation when marked as bad""" + + expert_review_status: Optional[Literal["good", "bad"]] = None + """Expert review status: 'good' or 'bad'""" + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -409,6 +421,9 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + similar_query_log_guardrail_explanation: Optional[str] = None + """Explanation from a similar bad query log that caused this to be guardrailed""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 92583090..cae996c6 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -387,6 +387,18 @@ class QueryLogRetrieveResponse(BaseModel): Used to log tool calls in the query log. """ + expert_review_created_at: Optional[datetime] = None + """When the expert review was created""" + + expert_review_created_by_user_id: Optional[str] = None + """ID of the user who created the expert review""" + + expert_review_explanation: Optional[str] = None + """Expert explanation when marked as bad""" + + expert_review_status: Optional[Literal["good", "bad"]] = None + """Expert review status: 'good' or 'bad'""" + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -416,6 +428,9 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + similar_query_log_guardrail_explanation: Optional[str] = None + """Explanation from a similar bad query log that caused this to be guardrailed""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index ed764766..1ef0e6f0 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -390,6 +390,18 @@ class QueryLog(BaseModel): Used to log tool calls in the query log. """ + expert_review_created_at: Optional[datetime] = None + """When the expert review was created""" + + expert_review_created_by_user_id: Optional[str] = None + """ID of the user who created the expert review""" + + expert_review_explanation: Optional[str] = None + """Expert explanation when marked as bad""" + + expert_review_status: Optional[Literal["good", "bad"]] = None + """Expert review status: 'good' or 'bad'""" + guardrail_evals: Optional[List[str]] = None """Evals that should trigger guardrail""" @@ -416,6 +428,9 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + similar_query_log_guardrail_explanation: Optional[str] = None + """Explanation from a similar bad query log that caused this to be guardrailed""" + tools: Optional[List[QueryLogTool]] = None """Tools to use for the LLM call. diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index ab8dcc66..1bfc3bc0 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -100,6 +100,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + expert_review_status="good", failed_evals=["string"], guardrailed=True, has_tool_calls=True, @@ -221,6 +222,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + expert_review_status="good", failed_evals=["string"], guardrailed=True, has_tool_calls=True, @@ -287,6 +289,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + expert_review_status="good", failed_evals=["string"], guardrailed=True, has_tool_calls=True, @@ -519,6 +522,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + expert_review_status="good", failed_evals=["string"], guardrailed=True, has_tool_calls=True, @@ -640,6 +644,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + expert_review_status="good", failed_evals=["string"], guardrailed=True, has_tool_calls=True, @@ -706,6 +711,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), custom_metadata="custom_metadata", + expert_review_status="good", failed_evals=["string"], guardrailed=True, has_tool_calls=True, From f18e4eee987075bc190da0d06c845bad3a1719b1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 19:17:26 +0000 Subject: [PATCH 252/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 30 +++++++++++ src/codex/types/project_create_params.py | 4 ++ src/codex/types/project_list_response.py | 4 ++ src/codex/types/project_retrieve_response.py | 4 ++ src/codex/types/project_return_schema.py | 4 ++ src/codex/types/project_update_params.py | 4 ++ .../query_log_list_by_group_params.py | 6 +++ .../query_log_list_by_group_response.py | 52 +++++++++++++++++++ .../projects/query_log_list_groups_params.py | 6 +++ .../types/projects/query_log_list_params.py | 6 +++ .../api_resources/projects/test_query_logs.py | 6 +++ tests/api_resources/test_projects.py | 8 +++ 13 files changed, 135 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f4ab1b18..364c7d01 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: b752c79a72b1b72b4b5aea40e27c16c5 +openapi_spec_hash: ef5807baa380babd037f9b6271761eae config_hash: 6c3ad84d97bf1d0989ad2ec0cae64078 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 2dfe62ea..9f89d150 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -118,6 +118,7 @@ def list( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, + search_text: Optional[str] | NotGiven = NOT_GIVEN, sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, @@ -150,6 +151,9 @@ def list( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + search_text: Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + sort: Field or score to sort by. Available fields: 'created_at', 'primary_eval_issue_score'. @@ -197,6 +201,7 @@ def list( "order": order, "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, + "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, @@ -268,6 +273,7 @@ def list_by_group( ] | NotGiven = NOT_GIVEN, remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, + search_text: Optional[str] | NotGiven = NOT_GIVEN, sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, @@ -304,6 +310,9 @@ def list_by_group( remediation_ids: List of groups to list child logs for + search_text: Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + sort: Field or score to sort by. Available fields: 'created_at', 'primary_eval_issue_score'. @@ -352,6 +361,7 @@ def list_by_group( "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "remediation_ids": remediation_ids, + "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, @@ -382,6 +392,7 @@ def list_groups( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, + search_text: Optional[str] | NotGiven = NOT_GIVEN, sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, @@ -416,6 +427,9 @@ def list_groups( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + search_text: Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + sort: Field or score to sort by. Available fields: 'created_at', 'custom_rank', 'impact_score', @@ -465,6 +479,7 @@ def list_groups( "order": order, "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, + "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, @@ -625,6 +640,7 @@ def list( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, + search_text: Optional[str] | NotGiven = NOT_GIVEN, sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, @@ -657,6 +673,9 @@ def list( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + search_text: Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + sort: Field or score to sort by. Available fields: 'created_at', 'primary_eval_issue_score'. @@ -704,6 +723,7 @@ def list( "order": order, "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, + "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, @@ -777,6 +797,7 @@ async def list_by_group( ] | NotGiven = NOT_GIVEN, remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, + search_text: Optional[str] | NotGiven = NOT_GIVEN, sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, @@ -813,6 +834,9 @@ async def list_by_group( remediation_ids: List of groups to list child logs for + search_text: Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + sort: Field or score to sort by. Available fields: 'created_at', 'primary_eval_issue_score'. @@ -861,6 +885,7 @@ async def list_by_group( "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, "remediation_ids": remediation_ids, + "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, @@ -891,6 +916,7 @@ def list_groups( List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] | NotGiven = NOT_GIVEN, + search_text: Optional[str] | NotGiven = NOT_GIVEN, sort: Optional[str] | NotGiven = NOT_GIVEN, tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, @@ -925,6 +951,9 @@ def list_groups( primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) + search_text: Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + sort: Field or score to sort by. Available fields: 'created_at', 'custom_rank', 'impact_score', @@ -974,6 +1003,7 @@ def list_groups( "order": order, "passed_evals": passed_evals, "primary_eval_issue": primary_eval_issue, + "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, "was_cache_hit": was_cache_hit, diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index c75023f0..0d4a4f57 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -333,6 +333,10 @@ class Config(TypedDict, total=False): query_use_llm_matching: bool + question_match_llm_prompt: str + + question_match_llm_prompt_with_answer: str + tlm_evals_model: str upper_llm_match_distance_threshold: float diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 4ac38497..db1666cf 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -323,6 +323,10 @@ class ProjectConfig(BaseModel): query_use_llm_matching: Optional[bool] = None + question_match_llm_prompt: Optional[str] = None + + question_match_llm_prompt_with_answer: Optional[str] = None + tlm_evals_model: Optional[str] = None upper_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 6e87d655..19abb67f 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -322,6 +322,10 @@ class Config(BaseModel): query_use_llm_matching: Optional[bool] = None + question_match_llm_prompt: Optional[str] = None + + question_match_llm_prompt_with_answer: Optional[str] = None + tlm_evals_model: Optional[str] = None upper_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index bb087cd0..cb114db9 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -322,6 +322,10 @@ class Config(BaseModel): query_use_llm_matching: Optional[bool] = None + question_match_llm_prompt: Optional[str] = None + + question_match_llm_prompt_with_answer: Optional[str] = None + tlm_evals_model: Optional[str] = None upper_llm_match_distance_threshold: Optional[float] = None diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index c550b436..9b32bb6d 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -331,6 +331,10 @@ class Config(TypedDict, total=False): query_use_llm_matching: bool + question_match_llm_prompt: str + + question_match_llm_prompt_with_answer: str + tlm_evals_model: str upper_llm_match_distance_threshold: float diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 73d18445..17a260bb 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -54,6 +54,12 @@ class QueryLogListByGroupParams(TypedDict, total=False): remediation_ids: SequenceNotStr[str] """List of groups to list child logs for""" + search_text: Optional[str] + """ + Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + """ + sort: Optional[str] """Field or score to sort by. diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 5cc3ac96..f6156eb2 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -45,6 +45,7 @@ "QueryLogsByGroupQueryLogMessageChatCompletionDeveloperMessageParamContentUnionMember1", "QueryLogsByGroupQueryLogTool", "QueryLogsByGroupQueryLogToolFunction", + "Filters", ] @@ -456,11 +457,62 @@ class QueryLogsByGroup(BaseModel): total_count: int +class Filters(BaseModel): + custom_metadata_dict: Optional[object] = None + + created_at_end: Optional[datetime] = None + """Filter logs created at or before this timestamp""" + + created_at_start: Optional[datetime] = None + """Filter logs created at or after this timestamp""" + + custom_metadata: Optional[str] = None + """Filter by custom metadata as JSON string: {"key1": "value1", "key2": "value2"}""" + + expert_review_status: Optional[Literal["good", "bad"]] = None + """Filter by expert review status""" + + failed_evals: Optional[List[str]] = None + """Filter by evals that failed""" + + guardrailed: Optional[bool] = None + """Filter by guardrailed status""" + + has_tool_calls: Optional[bool] = None + """Filter by whether the query log has tool calls""" + + needs_review: Optional[bool] = None + """Filter logs that need review""" + + passed_evals: Optional[List[str]] = None + """Filter by evals that passed""" + + primary_eval_issue: Optional[ + List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] + ] = None + """Filter logs that have ANY of these primary evaluation issues (OR operation)""" + + search_text: Optional[str] = None + """ + Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + """ + + tool_call_names: Optional[List[str]] = None + """Filter by names of tools called in the assistant response""" + + was_cache_hit: Optional[bool] = None + """Filter by cache hit status""" + + class QueryLogListByGroupResponse(BaseModel): custom_metadata_columns: List[str] """Columns of the custom metadata""" query_logs_by_group: Dict[str, QueryLogsByGroup] + filters: Optional[Filters] = None + """Applied filters for the query""" + tool_names: Optional[List[str]] = None """Names of the tools available in queries""" diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index 849a73ae..ece65b16 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -51,6 +51,12 @@ class QueryLogListGroupsParams(TypedDict, total=False): ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" + search_text: Optional[str] + """ + Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + """ + sort: Optional[str] """Field or score to sort by. diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 2d717ae1..eb7858a6 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -48,6 +48,12 @@ class QueryLogListParams(TypedDict, total=False): ] """Filter logs that have ANY of these primary evaluation issues (OR operation)""" + search_text: Optional[str] + """ + Case-insensitive search across evaluated_response and question fields + (original_question if available, otherwise question) + """ + sort: Optional[str] """Field or score to sort by. diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 1bfc3bc0..51262fe0 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -109,6 +109,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], + search_text="search_text", sort="created_at", tool_call_names=["string"], was_cache_hit=True, @@ -233,6 +234,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + search_text="search_text", sort="created_at", tool_call_names=["string"], was_cache_hit=True, @@ -299,6 +301,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], + search_text="search_text", sort="created_at", tool_call_names=["string"], was_cache_hit=True, @@ -531,6 +534,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], + search_text="search_text", sort="created_at", tool_call_names=["string"], was_cache_hit=True, @@ -655,6 +659,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod passed_evals=["string"], primary_eval_issue=["hallucination"], remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + search_text="search_text", sort="created_at", tool_call_names=["string"], was_cache_hit=True, @@ -721,6 +726,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex order="asc", passed_evals=["string"], primary_eval_issue=["hallucination"], + search_text="search_text", sort="created_at", tool_call_names=["string"], was_cache_hit=True, diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 04eef999..3f3e9b9d 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -118,6 +118,8 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "question_match_llm_prompt": "question_match_llm_prompt", + "question_match_llm_prompt_with_answer": "question_match_llm_prompt_with_answer", "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, @@ -294,6 +296,8 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "question_match_llm_prompt": "question_match_llm_prompt", + "question_match_llm_prompt_with_answer": "question_match_llm_prompt_with_answer", "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, @@ -794,6 +798,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "question_match_llm_prompt": "question_match_llm_prompt", + "question_match_llm_prompt_with_answer": "question_match_llm_prompt_with_answer", "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, @@ -970,6 +976,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "lower_llm_match_distance_threshold": 0, "max_distance": 0, "query_use_llm_matching": True, + "question_match_llm_prompt": "question_match_llm_prompt", + "question_match_llm_prompt_with_answer": "question_match_llm_prompt_with_answer", "tlm_evals_model": "tlm_evals_model", "upper_llm_match_distance_threshold": 0, }, From 0edc98be552de21eb85c83b0f036d4d41ac11cec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 02:18:33 +0000 Subject: [PATCH 253/320] chore(internal): update pydantic dependency --- requirements-dev.lock | 7 +++++-- requirements.lock | 7 +++++-- src/codex/_models.py | 14 ++++++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index ff0a2412..2aaac36f 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -88,9 +88,9 @@ pluggy==1.5.0 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via codex-sdk -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic pygments==2.18.0 # via rich @@ -126,6 +126,9 @@ typing-extensions==4.12.2 # via pydantic # via pydantic-core # via pyright + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic virtualenv==20.24.5 # via nox yarl==1.20.0 diff --git a/requirements.lock b/requirements.lock index bde9133e..a0182743 100644 --- a/requirements.lock +++ b/requirements.lock @@ -55,9 +55,9 @@ multidict==6.4.4 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via codex-sdk -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic sniffio==1.3.0 # via anyio @@ -68,5 +68,8 @@ typing-extensions==4.12.2 # via multidict # via pydantic # via pydantic-core + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic yarl==1.20.0 # via aiohttp diff --git a/src/codex/_models.py b/src/codex/_models.py index 3a6017ef..6a3cd1d2 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -256,7 +256,7 @@ def model_dump( mode: Literal["json", "python"] | str = "python", include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, @@ -264,6 +264,7 @@ def model_dump( warnings: bool | Literal["none", "warn", "error"] = True, context: dict[str, Any] | None = None, serialize_as_any: bool = False, + fallback: Callable[[Any], Any] | None = None, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -295,10 +296,12 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") 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, @@ -313,13 +316,14 @@ def model_dump_json( indent: int | None = None, include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: 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 @@ -348,11 +352,13 @@ 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") 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, From a48a878f0e7304325fc397653ad0be1e4564a680 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 02:17:09 +0000 Subject: [PATCH 254/320] feat(api): api update --- .stats.yml | 4 +- api.md | 13 - src/codex/_client.py | 10 +- src/codex/resources/__init__.py | 14 - src/codex/resources/projects/projects.py | 20 +- src/codex/resources/tlm.py | 677 --------------------- src/codex/types/__init__.py | 4 - src/codex/types/project_validate_params.py | 11 +- src/codex/types/tlm_prompt_params.py | 140 ----- src/codex/types/tlm_prompt_response.py | 15 - src/codex/types/tlm_score_params.py | 142 ----- src/codex/types/tlm_score_response.py | 13 - tests/api_resources/test_projects.py | 2 - tests/api_resources/test_tlm.py | 268 -------- 14 files changed, 9 insertions(+), 1324 deletions(-) delete mode 100644 src/codex/resources/tlm.py delete mode 100644 src/codex/types/tlm_prompt_params.py delete mode 100644 src/codex/types/tlm_prompt_response.py delete mode 100644 src/codex/types/tlm_score_params.py delete mode 100644 src/codex/types/tlm_score_response.py delete mode 100644 tests/api_resources/test_tlm.py diff --git a/.stats.yml b/.stats.yml index 364c7d01..c07ebda0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 56 -openapi_spec_hash: ef5807baa380babd037f9b6271761eae +configured_endpoints: 54 +openapi_spec_hash: f263c6c6d8d75a8f7c1e9c65188e7ef2 config_hash: 6c3ad84d97bf1d0989ad2ec0cae64078 diff --git a/api.md b/api.md index a78c4fc4..dc60687a 100644 --- a/api.md +++ b/api.md @@ -252,16 +252,3 @@ Methods: - client.projects.remediations.pause(remediation_id, \*, project_id) -> RemediationPauseResponse - client.projects.remediations.publish(remediation_id, \*, project_id) -> RemediationPublishResponse - client.projects.remediations.unpause(remediation_id, \*, project_id) -> RemediationUnpauseResponse - -# Tlm - -Types: - -```python -from codex.types import TlmPromptResponse, TlmScoreResponse -``` - -Methods: - -- client.tlm.prompt(\*\*params) -> TlmPromptResponse -- client.tlm.score(\*\*params) -> TlmScoreResponse diff --git a/src/codex/_client.py b/src/codex/_client.py index 352d0dbc..1a0e8839 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -22,7 +22,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import tlm, health +from .resources import health from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError from ._base_client import ( @@ -58,7 +58,6 @@ class Codex(SyncAPIClient): organizations: organizations.OrganizationsResource users: users.UsersResource projects: projects.ProjectsResource - tlm: tlm.TlmResource with_raw_response: CodexWithRawResponse with_streaming_response: CodexWithStreamedResponse @@ -143,7 +142,6 @@ def __init__( self.organizations = organizations.OrganizationsResource(self) self.users = users.UsersResource(self) self.projects = projects.ProjectsResource(self) - self.tlm = tlm.TlmResource(self) self.with_raw_response = CodexWithRawResponse(self) self.with_streaming_response = CodexWithStreamedResponse(self) @@ -304,7 +302,6 @@ class AsyncCodex(AsyncAPIClient): organizations: organizations.AsyncOrganizationsResource users: users.AsyncUsersResource projects: projects.AsyncProjectsResource - tlm: tlm.AsyncTlmResource with_raw_response: AsyncCodexWithRawResponse with_streaming_response: AsyncCodexWithStreamedResponse @@ -389,7 +386,6 @@ def __init__( self.organizations = organizations.AsyncOrganizationsResource(self) self.users = users.AsyncUsersResource(self) self.projects = projects.AsyncProjectsResource(self) - self.tlm = tlm.AsyncTlmResource(self) self.with_raw_response = AsyncCodexWithRawResponse(self) self.with_streaming_response = AsyncCodexWithStreamedResponse(self) @@ -551,7 +547,6 @@ def __init__(self, client: Codex) -> None: self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations) self.users = users.UsersResourceWithRawResponse(client.users) self.projects = projects.ProjectsResourceWithRawResponse(client.projects) - self.tlm = tlm.TlmResourceWithRawResponse(client.tlm) class AsyncCodexWithRawResponse: @@ -560,7 +555,6 @@ def __init__(self, client: AsyncCodex) -> None: self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations) self.users = users.AsyncUsersResourceWithRawResponse(client.users) self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) - self.tlm = tlm.AsyncTlmResourceWithRawResponse(client.tlm) class CodexWithStreamedResponse: @@ -569,7 +563,6 @@ def __init__(self, client: Codex) -> None: self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.UsersResourceWithStreamingResponse(client.users) self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) - self.tlm = tlm.TlmResourceWithStreamingResponse(client.tlm) class AsyncCodexWithStreamedResponse: @@ -578,7 +571,6 @@ def __init__(self, client: AsyncCodex) -> None: self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations) self.users = users.AsyncUsersResourceWithStreamingResponse(client.users) self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) - self.tlm = tlm.AsyncTlmResourceWithStreamingResponse(client.tlm) Client = Codex diff --git a/src/codex/resources/__init__.py b/src/codex/resources/__init__.py index f91f0e43..b96b725a 100644 --- a/src/codex/resources/__init__.py +++ b/src/codex/resources/__init__.py @@ -1,13 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from .tlm import ( - TlmResource, - AsyncTlmResource, - TlmResourceWithRawResponse, - AsyncTlmResourceWithRawResponse, - TlmResourceWithStreamingResponse, - AsyncTlmResourceWithStreamingResponse, -) from .users import ( UsersResource, AsyncUsersResource, @@ -66,10 +58,4 @@ "AsyncProjectsResourceWithRawResponse", "ProjectsResourceWithStreamingResponse", "AsyncProjectsResourceWithStreamingResponse", - "TlmResource", - "AsyncTlmResource", - "TlmResourceWithRawResponse", - "AsyncTlmResourceWithRawResponse", - "TlmResourceWithStreamingResponse", - "AsyncTlmResourceWithStreamingResponse", ] diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index f0ef92ab..f19c4721 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -456,7 +456,6 @@ def validate( eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, - prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, @@ -576,12 +575,8 @@ def validate( When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. This parameter has no effect when `disable_trustworthiness` is True. - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - - prompt: The prompt to use for the TLM call. If not provided, the prompt will be - generated from the messages. + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. @@ -625,7 +620,6 @@ def validate( "eval_scores": eval_scores, "messages": messages, "options": options, - "prompt": prompt, "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, @@ -1034,7 +1028,6 @@ async def validate( eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, - prompt: Optional[str] | NotGiven = NOT_GIVEN, quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, task: Optional[str] | NotGiven = NOT_GIVEN, @@ -1154,12 +1147,8 @@ async def validate( When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. This parameter has no effect when `disable_trustworthiness` is True. - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - - prompt: The prompt to use for the TLM call. If not provided, the prompt will be - generated from the messages. + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. @@ -1203,7 +1192,6 @@ async def validate( "eval_scores": eval_scores, "messages": messages, "options": options, - "prompt": prompt, "quality_preset": quality_preset, "rewritten_question": rewritten_question, "task": task, diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py deleted file mode 100644 index de652ef9..00000000 --- a/src/codex/resources/tlm.py +++ /dev/null @@ -1,677 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Literal - -import httpx - -from ..types import tlm_score_params, tlm_prompt_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr -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.tlm_score_response import TlmScoreResponse -from ..types.tlm_prompt_response import TlmPromptResponse - -__all__ = ["TlmResource", "AsyncTlmResource"] - - -class TlmResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> TlmResourceWithRawResponse: - """ - 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return TlmResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> TlmResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return TlmResourceWithStreamingResponse(self) - - def prompt( - self, - *, - prompt: str, - constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN, - quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - task: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> TlmPromptResponse: - """ - Prompts the TLM API. - - Args: - options: Typed dict of advanced configuration options for the Trustworthy Language Model. - Many of these configurations are determined by the quality preset selected - (learn about quality presets in the TLM [initialization method](./#class-tlm)). - Specifying TLMOptions values directly overrides any default values set from the - quality preset. - - For all options described below, higher settings will lead to longer runtimes - and may consume more tokens internally. You may not be able to run long prompts - (or prompts with long responses) in your account, unless your token/rate limits - are increased. If you hit token limit issues, try lower/less expensive - TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to - increase your limits. - - The default values corresponding to each quality preset are: - - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. - - By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base - `model`, and `max_tokens` is set to 512. You can set custom values for these - arguments regardless of the quality preset specified. - - Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", - "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", - "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use - (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. - If you experience token/rate-limit errors, try lowering this number. - For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Reduce this value to reduce runtimes. Higher values may improve trust scoring. - - num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. - The maximum number currently supported is 3. Lower values can reduce runtimes. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - This parameter has no effect when `disable_trustworthiness` is True. - - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. - Must be between 0 and 20. Lower values can reduce runtimes. - Measuring consistency helps quantify the epistemic uncertainty associated with - strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - This parameter has no effect when `disable_trustworthiness` is True. - - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the - trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include - "semantic" (based on natural language inference), - "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), - "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. - This parameter has no effect when `num_consistency_samples = 0`. - - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - This parameter has no effect when `disable_trustworthiness` is True. - - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. - - 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._post( - "/api/tlm/prompt", - body=maybe_transform( - { - "prompt": prompt, - "constrain_outputs": constrain_outputs, - "options": options, - "quality_preset": quality_preset, - "task": task, - }, - tlm_prompt_params.TlmPromptParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=TlmPromptResponse, - ) - - def score( - self, - *, - prompt: str, - response: str, - constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN, - quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - task: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> TlmScoreResponse: - """ - Scores the TLM API. - - TODO: - - - Track query count in DB - - Enforce hard cap on queries for users w/o credit card on file - - Args: - options: Typed dict of advanced configuration options for the Trustworthy Language Model. - Many of these configurations are determined by the quality preset selected - (learn about quality presets in the TLM [initialization method](./#class-tlm)). - Specifying TLMOptions values directly overrides any default values set from the - quality preset. - - For all options described below, higher settings will lead to longer runtimes - and may consume more tokens internally. You may not be able to run long prompts - (or prompts with long responses) in your account, unless your token/rate limits - are increased. If you hit token limit issues, try lower/less expensive - TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to - increase your limits. - - The default values corresponding to each quality preset are: - - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. - - By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base - `model`, and `max_tokens` is set to 512. You can set custom values for these - arguments regardless of the quality preset specified. - - Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", - "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", - "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use - (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. - If you experience token/rate-limit errors, try lowering this number. - For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Reduce this value to reduce runtimes. Higher values may improve trust scoring. - - num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. - The maximum number currently supported is 3. Lower values can reduce runtimes. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - This parameter has no effect when `disable_trustworthiness` is True. - - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. - Must be between 0 and 20. Lower values can reduce runtimes. - Measuring consistency helps quantify the epistemic uncertainty associated with - strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - This parameter has no effect when `disable_trustworthiness` is True. - - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the - trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include - "semantic" (based on natural language inference), - "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), - "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. - This parameter has no effect when `num_consistency_samples = 0`. - - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - This parameter has no effect when `disable_trustworthiness` is True. - - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. - - 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._post( - "/api/tlm/score", - body=maybe_transform( - { - "prompt": prompt, - "response": response, - "constrain_outputs": constrain_outputs, - "options": options, - "quality_preset": quality_preset, - "task": task, - }, - tlm_score_params.TlmScoreParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=TlmScoreResponse, - ) - - -class AsyncTlmResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncTlmResourceWithRawResponse: - """ - 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers - """ - return AsyncTlmResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncTlmResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response - """ - return AsyncTlmResourceWithStreamingResponse(self) - - async def prompt( - self, - *, - prompt: str, - constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - options: Optional[tlm_prompt_params.Options] | NotGiven = NOT_GIVEN, - quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - task: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> TlmPromptResponse: - """ - Prompts the TLM API. - - Args: - options: Typed dict of advanced configuration options for the Trustworthy Language Model. - Many of these configurations are determined by the quality preset selected - (learn about quality presets in the TLM [initialization method](./#class-tlm)). - Specifying TLMOptions values directly overrides any default values set from the - quality preset. - - For all options described below, higher settings will lead to longer runtimes - and may consume more tokens internally. You may not be able to run long prompts - (or prompts with long responses) in your account, unless your token/rate limits - are increased. If you hit token limit issues, try lower/less expensive - TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to - increase your limits. - - The default values corresponding to each quality preset are: - - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. - - By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base - `model`, and `max_tokens` is set to 512. You can set custom values for these - arguments regardless of the quality preset specified. - - Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", - "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", - "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use - (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. - If you experience token/rate-limit errors, try lowering this number. - For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Reduce this value to reduce runtimes. Higher values may improve trust scoring. - - num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. - The maximum number currently supported is 3. Lower values can reduce runtimes. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - This parameter has no effect when `disable_trustworthiness` is True. - - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. - Must be between 0 and 20. Lower values can reduce runtimes. - Measuring consistency helps quantify the epistemic uncertainty associated with - strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - This parameter has no effect when `disable_trustworthiness` is True. - - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the - trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include - "semantic" (based on natural language inference), - "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), - "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. - This parameter has no effect when `num_consistency_samples = 0`. - - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - This parameter has no effect when `disable_trustworthiness` is True. - - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. - - 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._post( - "/api/tlm/prompt", - body=await async_maybe_transform( - { - "prompt": prompt, - "constrain_outputs": constrain_outputs, - "options": options, - "quality_preset": quality_preset, - "task": task, - }, - tlm_prompt_params.TlmPromptParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=TlmPromptResponse, - ) - - async def score( - self, - *, - prompt: str, - response: str, - constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - options: Optional[tlm_score_params.Options] | NotGiven = NOT_GIVEN, - quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - task: Optional[str] | NotGiven = NOT_GIVEN, - # 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, - ) -> TlmScoreResponse: - """ - Scores the TLM API. - - TODO: - - - Track query count in DB - - Enforce hard cap on queries for users w/o credit card on file - - Args: - options: Typed dict of advanced configuration options for the Trustworthy Language Model. - Many of these configurations are determined by the quality preset selected - (learn about quality presets in the TLM [initialization method](./#class-tlm)). - Specifying TLMOptions values directly overrides any default values set from the - quality preset. - - For all options described below, higher settings will lead to longer runtimes - and may consume more tokens internally. You may not be able to run long prompts - (or prompts with long responses) in your account, unless your token/rate limits - are increased. If you hit token limit issues, try lower/less expensive - TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to - increase your limits. - - The default values corresponding to each quality preset are: - - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. - - By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base - `model`, and `max_tokens` is set to 512. You can set custom values for these - arguments regardless of the quality preset specified. - - Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", - "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", - "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use - (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. - If you experience token/rate-limit errors, try lowering this number. - For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Reduce this value to reduce runtimes. Higher values may improve trust scoring. - - num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. - The maximum number currently supported is 3. Lower values can reduce runtimes. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - This parameter has no effect when `disable_trustworthiness` is True. - - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. - Must be between 0 and 20. Lower values can reduce runtimes. - Measuring consistency helps quantify the epistemic uncertainty associated with - strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - This parameter has no effect when `disable_trustworthiness` is True. - - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the - trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include - "semantic" (based on natural language inference), - "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), - "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. - This parameter has no effect when `num_consistency_samples = 0`. - - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - This parameter has no effect when `disable_trustworthiness` is True. - - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - - quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. - - 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._post( - "/api/tlm/score", - body=await async_maybe_transform( - { - "prompt": prompt, - "response": response, - "constrain_outputs": constrain_outputs, - "options": options, - "quality_preset": quality_preset, - "task": task, - }, - tlm_score_params.TlmScoreParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=TlmScoreResponse, - ) - - -class TlmResourceWithRawResponse: - def __init__(self, tlm: TlmResource) -> None: - self._tlm = tlm - - self.prompt = to_raw_response_wrapper( - tlm.prompt, - ) - self.score = to_raw_response_wrapper( - tlm.score, - ) - - -class AsyncTlmResourceWithRawResponse: - def __init__(self, tlm: AsyncTlmResource) -> None: - self._tlm = tlm - - self.prompt = async_to_raw_response_wrapper( - tlm.prompt, - ) - self.score = async_to_raw_response_wrapper( - tlm.score, - ) - - -class TlmResourceWithStreamingResponse: - def __init__(self, tlm: TlmResource) -> None: - self._tlm = tlm - - self.prompt = to_streamed_response_wrapper( - tlm.prompt, - ) - self.score = to_streamed_response_wrapper( - tlm.score, - ) - - -class AsyncTlmResourceWithStreamingResponse: - def __init__(self, tlm: AsyncTlmResource) -> None: - self._tlm = tlm - - self.prompt = async_to_streamed_response_wrapper( - tlm.prompt, - ) - self.score = async_to_streamed_response_wrapper( - tlm.score, - ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index daa16358..322b513b 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -2,11 +2,7 @@ from __future__ import annotations -from .tlm_score_params import TlmScoreParams as TlmScoreParams -from .tlm_prompt_params import TlmPromptParams as TlmPromptParams -from .tlm_score_response import TlmScoreResponse as TlmScoreResponse from .project_list_params import ProjectListParams as ProjectListParams -from .tlm_prompt_response import TlmPromptResponse as TlmPromptResponse from .health_check_response import HealthCheckResponse as HealthCheckResponse from .project_create_params import ProjectCreateParams as ProjectCreateParams from .project_list_response import ProjectListResponse as ProjectListResponse diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 56e2ae97..0efa4309 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -180,15 +180,8 @@ class ProjectValidateParams(TypedDict, total=False): When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. This parameter has no effect when `disable_trustworthiness` is True. - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - """ - - prompt: Optional[str] - """The prompt to use for the TLM call. - - If not provided, the prompt will be generated from the messages. + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. """ quality_preset: Literal["best", "high", "medium", "low", "base"] diff --git a/src/codex/types/tlm_prompt_params.py b/src/codex/types/tlm_prompt_params.py deleted file mode 100644 index 6a2a9da3..00000000 --- a/src/codex/types/tlm_prompt_params.py +++ /dev/null @@ -1,140 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Iterable, Optional -from typing_extensions import Literal, Required, TypedDict - -from .._types import SequenceNotStr - -__all__ = ["TlmPromptParams", "Options"] - - -class TlmPromptParams(TypedDict, total=False): - prompt: Required[str] - - constrain_outputs: Optional[SequenceNotStr[str]] - - options: Optional[Options] - """ - Typed dict of advanced configuration options for the Trustworthy Language Model. - Many of these configurations are determined by the quality preset selected - (learn about quality presets in the TLM [initialization method](./#class-tlm)). - Specifying TLMOptions values directly overrides any default values set from the - quality preset. - - For all options described below, higher settings will lead to longer runtimes - and may consume more tokens internally. You may not be able to run long prompts - (or prompts with long responses) in your account, unless your token/rate limits - are increased. If you hit token limit issues, try lower/less expensive - TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to - increase your limits. - - The default values corresponding to each quality preset are: - - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. - - By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base - `model`, and `max_tokens` is set to 512. You can set custom values for these - arguments regardless of the quality preset specified. - - Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", - "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", - "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use - (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. - If you experience token/rate-limit errors, try lowering this number. - For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Reduce this value to reduce runtimes. Higher values may improve trust scoring. - - num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. - The maximum number currently supported is 3. Lower values can reduce runtimes. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - This parameter has no effect when `disable_trustworthiness` is True. - - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. - Must be between 0 and 20. Lower values can reduce runtimes. - Measuring consistency helps quantify the epistemic uncertainty associated with - strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - This parameter has no effect when `disable_trustworthiness` is True. - - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the - trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include - "semantic" (based on natural language inference), - "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), - "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. - This parameter has no effect when `num_consistency_samples = 0`. - - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - This parameter has no effect when `disable_trustworthiness` is True. - - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - """ - - quality_preset: Literal["best", "high", "medium", "low", "base"] - """The quality preset to use for the TLM or Trustworthy RAG API.""" - - task: Optional[str] - - -class Options(TypedDict, total=False): - custom_eval_criteria: Iterable[object] - - disable_persistence: bool - - disable_trustworthiness: bool - - log: SequenceNotStr[str] - - max_tokens: int - - model: str - - num_candidate_responses: int - - num_consistency_samples: int - - num_self_reflections: int - - reasoning_effort: str - - similarity_measure: str - - use_self_reflection: bool diff --git a/src/codex/types/tlm_prompt_response.py b/src/codex/types/tlm_prompt_response.py deleted file mode 100644 index d939c00e..00000000 --- a/src/codex/types/tlm_prompt_response.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional - -from .._models import BaseModel - -__all__ = ["TlmPromptResponse"] - - -class TlmPromptResponse(BaseModel): - response: str - - trustworthiness_score: float - - log: Optional[object] = None diff --git a/src/codex/types/tlm_score_params.py b/src/codex/types/tlm_score_params.py deleted file mode 100644 index cef4f490..00000000 --- a/src/codex/types/tlm_score_params.py +++ /dev/null @@ -1,142 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Iterable, Optional -from typing_extensions import Literal, Required, TypedDict - -from .._types import SequenceNotStr - -__all__ = ["TlmScoreParams", "Options"] - - -class TlmScoreParams(TypedDict, total=False): - prompt: Required[str] - - response: Required[str] - - constrain_outputs: Optional[SequenceNotStr[str]] - - options: Optional[Options] - """ - Typed dict of advanced configuration options for the Trustworthy Language Model. - Many of these configurations are determined by the quality preset selected - (learn about quality presets in the TLM [initialization method](./#class-tlm)). - Specifying TLMOptions values directly overrides any default values set from the - quality preset. - - For all options described below, higher settings will lead to longer runtimes - and may consume more tokens internally. You may not be able to run long prompts - (or prompts with long responses) in your account, unless your token/rate limits - are increased. If you hit token limit issues, try lower/less expensive - TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to - increase your limits. - - The default values corresponding to each quality preset are: - - - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"high"`. - - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, - `reasoning_effort` = `"none"`. - - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, - `reasoning_effort` = `"none"`. - - By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base - `model`, and `max_tokens` is set to 512. You can set custom values for these - arguments regardless of the quality preset specified. - - Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", - "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", - "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", - "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", - "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", - "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use - (better models yield better results, faster models yield faster results). - - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", - "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", - "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", - "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models - for low latency/costs: "gpt-4.1-nano", "nova-micro". - - log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. - For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. - - custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. - The expected input format is a list of dictionaries, where each dictionary has the following keys: - - name: Name of the evaluation criteria. - - criteria: Instructions specifying the evaluation criteria. - - max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. - If you experience token/rate-limit errors, try lowering this number. - For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. - - reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) - when generating alternative possible responses and reflecting on responses during trustworthiness scoring. - Reduce this value to reduce runtimes. Higher values may improve trust scoring. - - num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. - The maximum number currently supported is 3. Lower values can reduce runtimes. - Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. - This parameter has no effect when `disable_trustworthiness` is True. - - num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. - Must be between 0 and 20. Lower values can reduce runtimes. - Measuring consistency helps quantify the epistemic uncertainty associated with - strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. - TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. - This parameter has no effect when `disable_trustworthiness` is True. - - similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the - trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. - Supported similarity measures include - "semantic" (based on natural language inference), - "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), - "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), - and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. - This parameter has no effect when `num_consistency_samples = 0`. - - num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. - `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. - You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. - This parameter must be between 1 and 20. It has no effect on `TLM.score()`. - When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. - This parameter has no effect when `disable_trustworthiness` is True. - - disable_trustworthiness (bool, default = False): if True, trustworthiness scoring is disabled and TLM will not compute trust scores for responses. - This is useful when you only want to use custom evaluation criteria or when you want to minimize computational overhead and only need the base LLM response. - The following parameters will be ignored when `disable_trustworthiness` is True: `num_consistency_samples`, `num_self_reflections`, `num_candidate_responses`, `reasoning_effort`, `similarity_measure`. - """ - - quality_preset: Literal["best", "high", "medium", "low", "base"] - """The quality preset to use for the TLM or Trustworthy RAG API.""" - - task: Optional[str] - - -class Options(TypedDict, total=False): - custom_eval_criteria: Iterable[object] - - disable_persistence: bool - - disable_trustworthiness: bool - - log: SequenceNotStr[str] - - max_tokens: int - - model: str - - num_candidate_responses: int - - num_consistency_samples: int - - num_self_reflections: int - - reasoning_effort: str - - similarity_measure: str - - use_self_reflection: bool diff --git a/src/codex/types/tlm_score_response.py b/src/codex/types/tlm_score_response.py deleted file mode 100644 index e92b2e09..00000000 --- a/src/codex/types/tlm_score_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional - -from .._models import BaseModel - -__all__ = ["TlmScoreResponse"] - - -class TlmScoreResponse(BaseModel): - trustworthiness_score: float - - log: Optional[object] = None diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 3f3e9b9d..153c3aae 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -633,7 +633,6 @@ def test_method_validate_with_all_params(self, client: Codex) -> None: "similarity_measure": "similarity_measure", "use_self_reflection": True, }, - prompt="prompt", quality_preset="best", rewritten_question="rewritten_question", task="task", @@ -1313,7 +1312,6 @@ async def test_method_validate_with_all_params(self, async_client: AsyncCodex) - "similarity_measure": "similarity_measure", "use_self_reflection": True, }, - prompt="prompt", quality_preset="best", rewritten_question="rewritten_question", task="task", diff --git a/tests/api_resources/test_tlm.py b/tests/api_resources/test_tlm.py deleted file mode 100644 index 6c8c1770..00000000 --- a/tests/api_resources/test_tlm.py +++ /dev/null @@ -1,268 +0,0 @@ -# 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 codex import Codex, AsyncCodex -from codex.types import TlmScoreResponse, TlmPromptResponse -from tests.utils import assert_matches_type - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestTlm: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_prompt(self, client: Codex) -> None: - tlm = client.tlm.prompt( - prompt="prompt", - ) - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_prompt_with_all_params(self, client: Codex) -> None: - tlm = client.tlm.prompt( - prompt="prompt", - constrain_outputs=["string"], - options={ - "custom_eval_criteria": [{}], - "disable_persistence": True, - "disable_trustworthiness": True, - "log": ["string"], - "max_tokens": 0, - "model": "model", - "num_candidate_responses": 0, - "num_consistency_samples": 0, - "num_self_reflections": 0, - "reasoning_effort": "reasoning_effort", - "similarity_measure": "similarity_measure", - "use_self_reflection": True, - }, - quality_preset="best", - task="task", - ) - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_prompt(self, client: Codex) -> None: - response = client.tlm.with_raw_response.prompt( - prompt="prompt", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - tlm = response.parse() - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_prompt(self, client: Codex) -> None: - with client.tlm.with_streaming_response.prompt( - prompt="prompt", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - tlm = response.parse() - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_score(self, client: Codex) -> None: - tlm = client.tlm.score( - prompt="prompt", - response="response", - ) - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_score_with_all_params(self, client: Codex) -> None: - tlm = client.tlm.score( - prompt="prompt", - response="response", - constrain_outputs=["string"], - options={ - "custom_eval_criteria": [{}], - "disable_persistence": True, - "disable_trustworthiness": True, - "log": ["string"], - "max_tokens": 0, - "model": "model", - "num_candidate_responses": 0, - "num_consistency_samples": 0, - "num_self_reflections": 0, - "reasoning_effort": "reasoning_effort", - "similarity_measure": "similarity_measure", - "use_self_reflection": True, - }, - quality_preset="best", - task="task", - ) - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_score(self, client: Codex) -> None: - response = client.tlm.with_raw_response.score( - prompt="prompt", - response="response", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - tlm = response.parse() - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_score(self, client: Codex) -> None: - with client.tlm.with_streaming_response.score( - prompt="prompt", - response="response", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - tlm = response.parse() - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncTlm: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_prompt(self, async_client: AsyncCodex) -> None: - tlm = await async_client.tlm.prompt( - prompt="prompt", - ) - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_prompt_with_all_params(self, async_client: AsyncCodex) -> None: - tlm = await async_client.tlm.prompt( - prompt="prompt", - constrain_outputs=["string"], - options={ - "custom_eval_criteria": [{}], - "disable_persistence": True, - "disable_trustworthiness": True, - "log": ["string"], - "max_tokens": 0, - "model": "model", - "num_candidate_responses": 0, - "num_consistency_samples": 0, - "num_self_reflections": 0, - "reasoning_effort": "reasoning_effort", - "similarity_measure": "similarity_measure", - "use_self_reflection": True, - }, - quality_preset="best", - task="task", - ) - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_prompt(self, async_client: AsyncCodex) -> None: - response = await async_client.tlm.with_raw_response.prompt( - prompt="prompt", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - tlm = await response.parse() - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_prompt(self, async_client: AsyncCodex) -> None: - async with async_client.tlm.with_streaming_response.prompt( - prompt="prompt", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - tlm = await response.parse() - assert_matches_type(TlmPromptResponse, tlm, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_score(self, async_client: AsyncCodex) -> None: - tlm = await async_client.tlm.score( - prompt="prompt", - response="response", - ) - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_score_with_all_params(self, async_client: AsyncCodex) -> None: - tlm = await async_client.tlm.score( - prompt="prompt", - response="response", - constrain_outputs=["string"], - options={ - "custom_eval_criteria": [{}], - "disable_persistence": True, - "disable_trustworthiness": True, - "log": ["string"], - "max_tokens": 0, - "model": "model", - "num_candidate_responses": 0, - "num_consistency_samples": 0, - "num_self_reflections": 0, - "reasoning_effort": "reasoning_effort", - "similarity_measure": "similarity_measure", - "use_self_reflection": True, - }, - quality_preset="best", - task="task", - ) - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_score(self, async_client: AsyncCodex) -> None: - response = await async_client.tlm.with_raw_response.score( - prompt="prompt", - response="response", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - tlm = await response.parse() - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_score(self, async_client: AsyncCodex) -> None: - async with async_client.tlm.with_streaming_response.score( - prompt="prompt", - response="response", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - tlm = await response.parse() - assert_matches_type(TlmScoreResponse, tlm, path=["response"]) - - assert cast(Any, response.is_closed) is True From 72614f021038e4852b841c0cf777ecf00facf5bc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 02:21:24 +0000 Subject: [PATCH 255/320] chore(types): change optional parameter type from NotGiven to Omit --- src/codex/__init__.py | 4 +- src/codex/_base_client.py | 18 +- src/codex/_client.py | 24 +- src/codex/_qs.py | 14 +- src/codex/_types.py | 29 ++- src/codex/_utils/_transform.py | 4 +- src/codex/_utils/_utils.py | 8 +- src/codex/resources/health.py | 10 +- .../organizations/billing/billing.py | 10 +- .../organizations/billing/card_details.py | 6 +- .../organizations/billing/plan_details.py | 6 +- .../organizations/billing/setup_intent.py | 6 +- .../resources/organizations/organizations.py | 14 +- src/codex/resources/projects/access_keys.py | 62 ++--- src/codex/resources/projects/evals.py | 190 +++++++------- src/codex/resources/projects/projects.py | 158 ++++++------ src/codex/resources/projects/query_logs.py | 234 +++++++++--------- src/codex/resources/projects/remediations.py | 94 +++---- src/codex/resources/users/myself/api_key.py | 10 +- src/codex/resources/users/myself/myself.py | 6 +- .../resources/users/myself/organizations.py | 6 +- src/codex/resources/users/users.py | 26 +- src/codex/resources/users/verification.py | 6 +- tests/test_transform.py | 11 +- 24 files changed, 486 insertions(+), 470 deletions(-) diff --git a/src/codex/__init__.py b/src/codex/__init__.py index 2b07cd12..373dabd4 100644 --- a/src/codex/__init__.py +++ b/src/codex/__init__.py @@ -3,7 +3,7 @@ import typing as _t from . import types -from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import ( ENVIRONMENTS, @@ -49,7 +49,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", "Omit", + "omit", "CodexError", "APIError", "APIStatusError", diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index e424fb76..e6febf3a 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -42,7 +42,6 @@ from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -57,6 +56,7 @@ RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping from ._compat import PYDANTIC_V1, model_copy, model_dump @@ -145,9 +145,9 @@ def __init__( def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - json: Body | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url self.json = json @@ -595,7 +595,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) @@ -825,7 +825,7 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1356,7 +1356,7 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1818,8 +1818,8 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} diff --git a/src/codex/_client.py b/src/codex/_client.py index 1a0e8839..308ce9ae 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Dict, Union, Mapping, cast +from typing import Any, Dict, Mapping, cast from typing_extensions import Self, Literal, override import httpx @@ -11,7 +11,6 @@ from . import _exceptions from ._qs import Querystring from ._types import ( - NOT_GIVEN, Omit, Headers, Timeout, @@ -19,6 +18,7 @@ Transport, ProxiesTypes, RequestOptions, + not_given, ) from ._utils import is_given, get_async_library from ._version import __version__ @@ -74,9 +74,9 @@ def __init__( auth_token: str | None = None, api_key: str | None = None, access_key: str | None = None, - environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + environment: Literal["production", "staging", "local"] | NotGiven = not_given, + base_url: str | httpx.URL | 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, @@ -214,9 +214,9 @@ def copy( access_key: str | None = None, environment: Literal["production", "staging", "local"] | 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, @@ -318,9 +318,9 @@ def __init__( auth_token: str | None = None, api_key: str | None = None, access_key: str | None = None, - environment: Literal["production", "staging", "local"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + environment: Literal["production", "staging", "local"] | NotGiven = not_given, + base_url: str | httpx.URL | 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, @@ -458,9 +458,9 @@ def copy( access_key: str | None = None, environment: Literal["production", "staging", "local"] | 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, diff --git a/src/codex/_qs.py b/src/codex/_qs.py index 274320ca..ada6fd3f 100644 --- a/src/codex/_qs.py +++ b/src/codex/_qs.py @@ -4,7 +4,7 @@ from urllib.parse import parse_qs, urlencode from typing_extensions import Literal, get_args -from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._types import NotGiven, not_given from ._utils import flatten _T = TypeVar("_T") @@ -41,8 +41,8 @@ def stringify( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> str: return urlencode( self.stringify_items( @@ -56,8 +56,8 @@ def stringify_items( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> list[tuple[str, str]]: opts = Options( qs=self, @@ -143,8 +143,8 @@ def __init__( self, qs: Querystring = _qs, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> None: self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/codex/_types.py b/src/codex/_types.py index 99d66e20..2e4695f9 100644 --- a/src/codex/_types.py +++ b/src/codex/_types.py @@ -117,18 +117,21 @@ class RequestOptions(TypedDict, total=False): # 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 ``` """ @@ -140,13 +143,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 @@ -156,8 +160,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}) ``` """ @@ -165,6 +169,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py index c19124f0..52075492 100644 --- a/src/codex/_utils/_transform.py +++ b/src/codex/_utils/_transform.py @@ -268,7 +268,7 @@ def _transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue @@ -434,7 +434,7 @@ async def _async_transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py index f0818595..50d59269 100644 --- a/src/codex/_utils/_utils.py +++ b/src/codex/_utils/_utils.py @@ -21,7 +21,7 @@ import sniffio -from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike +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 [] @@ -126,8 +126,8 @@ 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. diff --git a/src/codex/resources/health.py b/src/codex/resources/health.py index d74d23a5..d82d5cbb 100644 --- a/src/codex/resources/health.py +++ b/src/codex/resources/health.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 ( @@ -47,7 +47,7 @@ def check( 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, ) -> HealthCheckResponse: """Check the health of the application.""" return self._get( @@ -66,7 +66,7 @@ def db( 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, ) -> HealthCheckResponse: """Check the database connection.""" return self._get( @@ -106,7 +106,7 @@ async def check( 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, ) -> HealthCheckResponse: """Check the health of the application.""" return await self._get( @@ -125,7 +125,7 @@ async def db( 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, ) -> HealthCheckResponse: """Check the database connection.""" return await self._get( diff --git a/src/codex/resources/organizations/billing/billing.py b/src/codex/resources/organizations/billing/billing.py index 79c2a5c9..3a9eca12 100644 --- a/src/codex/resources/organizations/billing/billing.py +++ b/src/codex/resources/organizations/billing/billing.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 ( @@ -85,7 +85,7 @@ def invoices( 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, ) -> OrganizationBillingInvoicesSchema: """ Get invoices iFrame URL for an organization. @@ -118,7 +118,7 @@ def usage( 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, ) -> OrganizationBillingUsageSchema: """ Get usage iFrame URL for an organization. @@ -184,7 +184,7 @@ async def invoices( 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, ) -> OrganizationBillingInvoicesSchema: """ Get invoices iFrame URL for an organization. @@ -217,7 +217,7 @@ async def usage( 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, ) -> OrganizationBillingUsageSchema: """ Get usage iFrame URL for an organization. diff --git a/src/codex/resources/organizations/billing/card_details.py b/src/codex/resources/organizations/billing/card_details.py index 94cb8a70..2a3f6704 100644 --- a/src/codex/resources/organizations/billing/card_details.py +++ b/src/codex/resources/organizations/billing/card_details.py @@ -6,7 +6,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 ( @@ -50,7 +50,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, ) -> Optional[OrganizationBillingCardDetails]: """ Get card details for an organization. @@ -104,7 +104,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, ) -> Optional[OrganizationBillingCardDetails]: """ Get card details for an organization. diff --git a/src/codex/resources/organizations/billing/plan_details.py b/src/codex/resources/organizations/billing/plan_details.py index 6ff726e2..029c02f2 100644 --- a/src/codex/resources/organizations/billing/plan_details.py +++ b/src/codex/resources/organizations/billing/plan_details.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 ( @@ -48,7 +48,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, ) -> OrganizationBillingPlanDetails: """ Get plan details for an organization. @@ -104,7 +104,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, ) -> OrganizationBillingPlanDetails: """ Get plan details for an organization. diff --git a/src/codex/resources/organizations/billing/setup_intent.py b/src/codex/resources/organizations/billing/setup_intent.py index ba915c6a..4efa57d0 100644 --- a/src/codex/resources/organizations/billing/setup_intent.py +++ b/src/codex/resources/organizations/billing/setup_intent.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 ( @@ -48,7 +48,7 @@ def create( 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, ) -> OrganizationBillingSetupIntent: """ Create a setup intent for an organization. @@ -102,7 +102,7 @@ async def create( 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, ) -> OrganizationBillingSetupIntent: """ Create a setup intent for an organization. diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py index f1eb4d5e..32b60fea 100644 --- a/src/codex/resources/organizations/organizations.py +++ b/src/codex/resources/organizations/organizations.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 ( @@ -62,7 +62,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, ) -> OrganizationSchemaPublic: """ Get a single organization. @@ -95,7 +95,7 @@ def list_members( 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, ) -> OrganizationListMembersResponse: """ Get a list of organization members with their names and emails. @@ -128,7 +128,7 @@ def retrieve_permissions( 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, ) -> OrganizationRetrievePermissionsResponse: """ Get the user's permissions for this organization. @@ -186,7 +186,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, ) -> OrganizationSchemaPublic: """ Get a single organization. @@ -219,7 +219,7 @@ async def list_members( 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, ) -> OrganizationListMembersResponse: """ Get a list of organization members with their names and emails. @@ -252,7 +252,7 @@ async def retrieve_permissions( 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, ) -> OrganizationRetrievePermissionsResponse: """ Get the user's permissions for this organization. diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 15190030..d18cef0d 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -7,7 +7,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -51,18 +51,18 @@ def create( project_id: str, *, name: str, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + expires_at: Union[str, datetime, None] | Omit = omit, + x_client_library_version: str | Omit = omit, + x_integration_type: str | Omit = omit, + x_source: str | Omit = omit, + x_stainless_package_version: 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, ) -> AccessKeySchema: """ Create a new access key. @@ -115,7 +115,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, ) -> AccessKeySchema: """ Get a single access key. @@ -147,14 +147,14 @@ def update( *, project_id: str, name: str, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + expires_at: Union[str, datetime, None] | 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, ) -> AccessKeySchema: """ Update an existing access key. @@ -197,7 +197,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, ) -> AccessKeyListResponse: """ List all access keys for a project. @@ -231,7 +231,7 @@ def delete( 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, ) -> None: """ Delete an existing access key. @@ -266,7 +266,7 @@ def retrieve_project_id( 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, ) -> AccessKeyRetrieveProjectIDResponse: """Get the project ID from an access key.""" return self._get( @@ -287,7 +287,7 @@ def revoke( 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, ) -> None: """ Revoke an access key. @@ -340,18 +340,18 @@ async def create( project_id: str, *, name: str, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + expires_at: Union[str, datetime, None] | Omit = omit, + x_client_library_version: str | Omit = omit, + x_integration_type: str | Omit = omit, + x_source: str | Omit = omit, + x_stainless_package_version: 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, ) -> AccessKeySchema: """ Create a new access key. @@ -404,7 +404,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, ) -> AccessKeySchema: """ Get a single access key. @@ -436,14 +436,14 @@ async def update( *, project_id: str, name: str, - description: Optional[str] | NotGiven = NOT_GIVEN, - expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + description: Optional[str] | Omit = omit, + expires_at: Union[str, datetime, None] | 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, ) -> AccessKeySchema: """ Update an existing access key. @@ -486,7 +486,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, ) -> AccessKeyListResponse: """ List all access keys for a project. @@ -520,7 +520,7 @@ async def delete( 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, ) -> None: """ Delete an existing access key. @@ -555,7 +555,7 @@ async def retrieve_project_id( 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, ) -> AccessKeyRetrieveProjectIDResponse: """Get the project ID from an access key.""" return await self._get( @@ -576,7 +576,7 @@ async def revoke( 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, ) -> None: """ Revoke an access key. diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py index 9de41b79..54802918 100644 --- a/src/codex/resources/projects/evals.py +++ b/src/codex/resources/projects/evals.py @@ -7,7 +7,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import required_args, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -52,22 +52,22 @@ def create( criteria: str, eval_key: str, name: str, - context_identifier: Optional[str] | NotGiven = NOT_GIVEN, - enabled: bool | NotGiven = NOT_GIVEN, - is_default: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - query_identifier: Optional[str] | NotGiven = NOT_GIVEN, - response_identifier: Optional[str] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + context_identifier: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + is_default: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + query_identifier: Optional[str] | Omit = omit, + response_identifier: Optional[str] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: """ Create a new custom eval for a project. @@ -150,22 +150,22 @@ def update( criteria: str, body_eval_key: str, name: str, - context_identifier: Optional[str] | NotGiven = NOT_GIVEN, - enabled: bool | NotGiven = NOT_GIVEN, - is_default: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - query_identifier: Optional[str] | NotGiven = NOT_GIVEN, - response_identifier: Optional[str] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + context_identifier: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + is_default: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + query_identifier: Optional[str] | Omit = omit, + response_identifier: Optional[str] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: """ Update an existing eval for a project. @@ -220,18 +220,18 @@ def update( *, project_id: str, body_eval_key: str, - enabled: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + enabled: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: """ Update an existing eval for a project. @@ -270,25 +270,25 @@ def update( path_eval_key: str, *, project_id: str, - criteria: str | NotGiven = NOT_GIVEN, + criteria: str | Omit = omit, body_eval_key: str, - name: str | NotGiven = NOT_GIVEN, - context_identifier: Optional[str] | NotGiven = NOT_GIVEN, - enabled: bool | NotGiven = NOT_GIVEN, - is_default: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - query_identifier: Optional[str] | NotGiven = NOT_GIVEN, - response_identifier: Optional[str] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + name: str | Omit = omit, + context_identifier: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + is_default: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + query_identifier: Optional[str] | Omit = omit, + response_identifier: Optional[str] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") @@ -324,15 +324,15 @@ def list( self, project_id: str, *, - guardrails_only: bool | NotGiven = NOT_GIVEN, - limit: Optional[int] | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, + guardrails_only: bool | Omit = omit, + limit: Optional[int] | Omit = omit, + offset: 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, ) -> EvalListResponse: """ Get the evaluations config for a project with optional pagination. @@ -377,7 +377,7 @@ def delete( 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, ) -> ProjectReturnSchema: """ Remove a custom eval for a project. @@ -431,22 +431,22 @@ async def create( criteria: str, eval_key: str, name: str, - context_identifier: Optional[str] | NotGiven = NOT_GIVEN, - enabled: bool | NotGiven = NOT_GIVEN, - is_default: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - query_identifier: Optional[str] | NotGiven = NOT_GIVEN, - response_identifier: Optional[str] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + context_identifier: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + is_default: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + query_identifier: Optional[str] | Omit = omit, + response_identifier: Optional[str] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: """ Create a new custom eval for a project. @@ -529,22 +529,22 @@ async def update( criteria: str, body_eval_key: str, name: str, - context_identifier: Optional[str] | NotGiven = NOT_GIVEN, - enabled: bool | NotGiven = NOT_GIVEN, - is_default: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - query_identifier: Optional[str] | NotGiven = NOT_GIVEN, - response_identifier: Optional[str] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + context_identifier: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + is_default: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + query_identifier: Optional[str] | Omit = omit, + response_identifier: Optional[str] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: """ Update an existing eval for a project. @@ -599,18 +599,18 @@ async def update( *, project_id: str, body_eval_key: str, - enabled: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + enabled: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: """ Update an existing eval for a project. @@ -649,25 +649,25 @@ async def update( path_eval_key: str, *, project_id: str, - criteria: str | NotGiven = NOT_GIVEN, + criteria: str | Omit = omit, body_eval_key: str, - name: str | NotGiven = NOT_GIVEN, - context_identifier: Optional[str] | NotGiven = NOT_GIVEN, - enabled: bool | NotGiven = NOT_GIVEN, - is_default: bool | NotGiven = NOT_GIVEN, - priority: Optional[int] | NotGiven = NOT_GIVEN, - query_identifier: Optional[str] | NotGiven = NOT_GIVEN, - response_identifier: Optional[str] | NotGiven = NOT_GIVEN, - should_escalate: bool | NotGiven = NOT_GIVEN, - should_guardrail: bool | NotGiven = NOT_GIVEN, - threshold: float | NotGiven = NOT_GIVEN, - threshold_direction: Literal["above", "below"] | NotGiven = NOT_GIVEN, + name: str | Omit = omit, + context_identifier: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + is_default: bool | Omit = omit, + priority: Optional[int] | Omit = omit, + query_identifier: Optional[str] | Omit = omit, + response_identifier: Optional[str] | Omit = omit, + should_escalate: bool | Omit = omit, + should_guardrail: bool | Omit = omit, + threshold: float | Omit = omit, + threshold_direction: Literal["above", "below"] | 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, ) -> ProjectReturnSchema: if not project_id: raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") @@ -703,15 +703,15 @@ async def list( self, project_id: str, *, - guardrails_only: bool | NotGiven = NOT_GIVEN, - limit: Optional[int] | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, + guardrails_only: bool | Omit = omit, + limit: Optional[int] | Omit = omit, + offset: 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, ) -> EvalListResponse: """ Get the evaluations config for a project with optional pagination. @@ -756,7 +756,7 @@ async def delete( 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, ) -> ProjectReturnSchema: """ Remove a custom eval for a project. diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index f19c4721..2ed4dd2c 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -23,7 +23,7 @@ project_invite_sme_params, project_retrieve_analytics_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven, SequenceNotStr +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from .query_logs import ( @@ -110,14 +110,14 @@ def create( config: project_create_params.Config, name: str, organization_id: str, - auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, - description: Optional[str] | NotGiven = NOT_GIVEN, + auto_clustering_enabled: bool | Omit = omit, + description: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectReturnSchema: """ Create a new project. @@ -158,7 +158,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, ) -> ProjectRetrieveResponse: """ Get a single project. @@ -186,16 +186,16 @@ def update( self, project_id: str, *, - auto_clustering_enabled: Optional[bool] | NotGiven = NOT_GIVEN, - config: Optional[project_update_params.Config] | NotGiven = NOT_GIVEN, - description: Optional[str] | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, + auto_clustering_enabled: Optional[bool] | Omit = omit, + config: Optional[project_update_params.Config] | Omit = omit, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectReturnSchema: """ Update a project. @@ -231,19 +231,19 @@ def update( def list( self, *, - include_unaddressed_counts: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - organization_id: str | NotGiven = NOT_GIVEN, - query: Optional[str] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, + include_unaddressed_counts: bool | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + organization_id: str | Omit = omit, + query: Optional[str] | Omit = omit, + sort: Literal["created_at", "updated_at"] | 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, ) -> ProjectListResponse: """ List projects for organization. @@ -289,7 +289,7 @@ def delete( 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, ) -> None: """ Delete a project. @@ -323,7 +323,7 @@ def export( 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, ) -> object: """ Export all data for a project as a JSON file. @@ -359,7 +359,7 @@ def invite_sme( 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, ) -> ProjectInviteSmeResponse: """ Invite a subject matter expert to view a specific query log or remediation. @@ -397,14 +397,14 @@ def retrieve_analytics( self, project_id: str, *, - end: int | NotGiven = NOT_GIVEN, - start: int | NotGiven = NOT_GIVEN, + end: int | Omit = omit, + start: 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, ) -> ProjectRetrieveAnalyticsResponse: """ Get Project Analytics Route @@ -449,27 +449,27 @@ def validate( context: str, query: str, response: project_validate_params.Response, - use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN, - constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, - eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, - options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, - quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, - task: Optional[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, + use_llm_matching: Optional[bool] | Omit = omit, + constrain_outputs: Optional[SequenceNotStr[str]] | Omit = omit, + custom_eval_thresholds: Optional[Dict[str, float]] | Omit = omit, + custom_metadata: Optional[object] | Omit = omit, + eval_scores: Optional[Dict[str, float]] | Omit = omit, + messages: Iterable[project_validate_params.Message] | Omit = omit, + options: Optional[project_validate_params.Options] | Omit = omit, + quality_preset: Literal["best", "high", "medium", "low", "base"] | Omit = omit, + rewritten_question: Optional[str] | Omit = omit, + task: Optional[str] | Omit = omit, + tools: Optional[Iterable[project_validate_params.Tool]] | Omit = omit, + x_client_library_version: str | Omit = omit, + x_integration_type: str | Omit = omit, + x_source: str | Omit = omit, + x_stainless_package_version: 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, ) -> ProjectValidateResponse: """ Evaluate whether a response, given the provided query and context, is @@ -682,14 +682,14 @@ async def create( config: project_create_params.Config, name: str, organization_id: str, - auto_clustering_enabled: bool | NotGiven = NOT_GIVEN, - description: Optional[str] | NotGiven = NOT_GIVEN, + auto_clustering_enabled: bool | Omit = omit, + description: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectReturnSchema: """ Create a new project. @@ -730,7 +730,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, ) -> ProjectRetrieveResponse: """ Get a single project. @@ -758,16 +758,16 @@ async def update( self, project_id: str, *, - auto_clustering_enabled: Optional[bool] | NotGiven = NOT_GIVEN, - config: Optional[project_update_params.Config] | NotGiven = NOT_GIVEN, - description: Optional[str] | NotGiven = NOT_GIVEN, - name: Optional[str] | NotGiven = NOT_GIVEN, + auto_clustering_enabled: Optional[bool] | Omit = omit, + config: Optional[project_update_params.Config] | Omit = omit, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectReturnSchema: """ Update a project. @@ -803,19 +803,19 @@ async def update( async def list( self, *, - include_unaddressed_counts: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - organization_id: str | NotGiven = NOT_GIVEN, - query: Optional[str] | NotGiven = NOT_GIVEN, - sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, + include_unaddressed_counts: bool | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + organization_id: str | Omit = omit, + query: Optional[str] | Omit = omit, + sort: Literal["created_at", "updated_at"] | 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, ) -> ProjectListResponse: """ List projects for organization. @@ -861,7 +861,7 @@ async def delete( 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, ) -> None: """ Delete a project. @@ -895,7 +895,7 @@ async def export( 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, ) -> object: """ Export all data for a project as a JSON file. @@ -931,7 +931,7 @@ async def invite_sme( 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, ) -> ProjectInviteSmeResponse: """ Invite a subject matter expert to view a specific query log or remediation. @@ -969,14 +969,14 @@ async def retrieve_analytics( self, project_id: str, *, - end: int | NotGiven = NOT_GIVEN, - start: int | NotGiven = NOT_GIVEN, + end: int | Omit = omit, + start: 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, ) -> ProjectRetrieveAnalyticsResponse: """ Get Project Analytics Route @@ -1021,27 +1021,27 @@ async def validate( context: str, query: str, response: project_validate_params.Response, - use_llm_matching: Optional[bool] | NotGiven = NOT_GIVEN, - constrain_outputs: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - custom_eval_thresholds: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[object] | NotGiven = NOT_GIVEN, - eval_scores: Optional[Dict[str, float]] | NotGiven = NOT_GIVEN, - messages: Iterable[project_validate_params.Message] | NotGiven = NOT_GIVEN, - options: Optional[project_validate_params.Options] | NotGiven = NOT_GIVEN, - quality_preset: Literal["best", "high", "medium", "low", "base"] | NotGiven = NOT_GIVEN, - rewritten_question: Optional[str] | NotGiven = NOT_GIVEN, - task: Optional[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[project_validate_params.Tool]] | NotGiven = NOT_GIVEN, - x_client_library_version: str | NotGiven = NOT_GIVEN, - x_integration_type: str | NotGiven = NOT_GIVEN, - x_source: str | NotGiven = NOT_GIVEN, - x_stainless_package_version: str | NotGiven = NOT_GIVEN, + use_llm_matching: Optional[bool] | Omit = omit, + constrain_outputs: Optional[SequenceNotStr[str]] | Omit = omit, + custom_eval_thresholds: Optional[Dict[str, float]] | Omit = omit, + custom_metadata: Optional[object] | Omit = omit, + eval_scores: Optional[Dict[str, float]] | Omit = omit, + messages: Iterable[project_validate_params.Message] | Omit = omit, + options: Optional[project_validate_params.Options] | Omit = omit, + quality_preset: Literal["best", "high", "medium", "low", "base"] | Omit = omit, + rewritten_question: Optional[str] | Omit = omit, + task: Optional[str] | Omit = omit, + tools: Optional[Iterable[project_validate_params.Tool]] | Omit = omit, + x_client_library_version: str | Omit = omit, + x_integration_type: str | Omit = omit, + x_source: str | Omit = omit, + x_stainless_package_version: 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, ) -> ProjectValidateResponse: """ Evaluate whether a response, given the provided query and context, is diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 9f89d150..2720dd15 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -8,7 +8,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -73,7 +73,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, ) -> QueryLogRetrieveResponse: """ Get Query Log Route @@ -103,31 +103,31 @@ def list( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, - failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, - has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + custom_metadata: Optional[str] | Omit = omit, + expert_review_status: Optional[Literal["good", "bad"]] | Omit = omit, + failed_evals: Optional[SequenceNotStr[str]] | Omit = omit, + guardrailed: Optional[bool] | Omit = omit, + has_tool_calls: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] - | NotGiven = NOT_GIVEN, - search_text: Optional[str] | NotGiven = NOT_GIVEN, - sort: Optional[str] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + | Omit = omit, + search_text: Optional[str] | Omit = omit, + sort: Optional[str] | Omit = omit, + tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncOffsetPageQueryLogs[QueryLogListResponse]: """ List query logs by project ID. @@ -223,7 +223,7 @@ def add_user_feedback( 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, ) -> QueryLogAddUserFeedbackResponse: """ Add User Feedback Route @@ -256,33 +256,33 @@ def list_by_group( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, - failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, - has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - needs_review: Optional[bool] | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + custom_metadata: Optional[str] | Omit = omit, + expert_review_status: Optional[Literal["good", "bad"]] | Omit = omit, + failed_evals: Optional[SequenceNotStr[str]] | Omit = omit, + guardrailed: Optional[bool] | Omit = omit, + has_tool_calls: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + needs_review: Optional[bool] | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] - | NotGiven = NOT_GIVEN, - remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, - search_text: Optional[str] | NotGiven = NOT_GIVEN, - sort: Optional[str] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + | Omit = omit, + remediation_ids: SequenceNotStr[str] | Omit = omit, + search_text: Optional[str] | Omit = omit, + sort: Optional[str] | Omit = omit, + tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> QueryLogListByGroupResponse: """ List query log group by remediation ID. @@ -376,32 +376,32 @@ def list_groups( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, - failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, - has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - needs_review: Optional[bool] | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + custom_metadata: Optional[str] | Omit = omit, + expert_review_status: Optional[Literal["good", "bad"]] | Omit = omit, + failed_evals: Optional[SequenceNotStr[str]] | Omit = omit, + guardrailed: Optional[bool] | Omit = omit, + has_tool_calls: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + needs_review: Optional[bool] | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] - | NotGiven = NOT_GIVEN, - search_text: Optional[str] | NotGiven = NOT_GIVEN, - sort: Optional[str] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + | Omit = omit, + search_text: Optional[str] | Omit = omit, + sort: Optional[str] | Omit = omit, + tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse]: """ List query log groups by project ID. @@ -500,7 +500,7 @@ def start_remediation( 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, ) -> QueryLogStartRemediationResponse: """ Start Remediation Route @@ -537,7 +537,7 @@ def update_metadata( 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, ) -> QueryLogUpdateMetadataResponse: """ Update Metadata Route @@ -595,7 +595,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, ) -> QueryLogRetrieveResponse: """ Get Query Log Route @@ -625,31 +625,31 @@ def list( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, - failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, - has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + custom_metadata: Optional[str] | Omit = omit, + expert_review_status: Optional[Literal["good", "bad"]] | Omit = omit, + failed_evals: Optional[SequenceNotStr[str]] | Omit = omit, + guardrailed: Optional[bool] | Omit = omit, + has_tool_calls: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] - | NotGiven = NOT_GIVEN, - search_text: Optional[str] | NotGiven = NOT_GIVEN, - sort: Optional[str] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + | Omit = omit, + search_text: Optional[str] | Omit = omit, + sort: Optional[str] | Omit = omit, + tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[QueryLogListResponse, AsyncOffsetPageQueryLogs[QueryLogListResponse]]: """ List query logs by project ID. @@ -745,7 +745,7 @@ async def add_user_feedback( 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, ) -> QueryLogAddUserFeedbackResponse: """ Add User Feedback Route @@ -780,33 +780,33 @@ async def list_by_group( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, - failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, - has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - needs_review: Optional[bool] | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + custom_metadata: Optional[str] | Omit = omit, + expert_review_status: Optional[Literal["good", "bad"]] | Omit = omit, + failed_evals: Optional[SequenceNotStr[str]] | Omit = omit, + guardrailed: Optional[bool] | Omit = omit, + has_tool_calls: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + needs_review: Optional[bool] | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] - | NotGiven = NOT_GIVEN, - remediation_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, - search_text: Optional[str] | NotGiven = NOT_GIVEN, - sort: Optional[str] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + | Omit = omit, + remediation_ids: SequenceNotStr[str] | Omit = omit, + search_text: Optional[str] | Omit = omit, + sort: Optional[str] | Omit = omit, + tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> QueryLogListByGroupResponse: """ List query log group by remediation ID. @@ -900,32 +900,32 @@ def list_groups( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - custom_metadata: Optional[str] | NotGiven = NOT_GIVEN, - expert_review_status: Optional[Literal["good", "bad"]] | NotGiven = NOT_GIVEN, - failed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - guardrailed: Optional[bool] | NotGiven = NOT_GIVEN, - has_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - needs_review: Optional[bool] | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - passed_evals: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + custom_metadata: Optional[str] | Omit = omit, + expert_review_status: Optional[Literal["good", "bad"]] | Omit = omit, + failed_evals: Optional[SequenceNotStr[str]] | Omit = omit, + guardrailed: Optional[bool] | Omit = omit, + has_tool_calls: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + needs_review: Optional[bool] | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, primary_eval_issue: Optional[ List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query", "ungrounded"]] ] - | NotGiven = NOT_GIVEN, - search_text: Optional[str] | NotGiven = NOT_GIVEN, - sort: Optional[str] | NotGiven = NOT_GIVEN, - tool_call_names: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN, - was_cache_hit: Optional[bool] | NotGiven = NOT_GIVEN, + | Omit = omit, + search_text: Optional[str] | Omit = omit, + sort: Optional[str] | Omit = omit, + tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[QueryLogListGroupsResponse, AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse]]: """ List query log groups by project ID. @@ -1024,7 +1024,7 @@ async def start_remediation( 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, ) -> QueryLogStartRemediationResponse: """ Start Remediation Route @@ -1061,7 +1061,7 @@ async def update_metadata( 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, ) -> QueryLogUpdateMetadataResponse: """ Update Metadata Route diff --git a/src/codex/resources/projects/remediations.py b/src/codex/resources/projects/remediations.py index ea0168c7..11642624 100644 --- a/src/codex/resources/projects/remediations.py +++ b/src/codex/resources/projects/remediations.py @@ -8,7 +8,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -65,14 +65,14 @@ def create( project_id: str, *, question: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + answer: Optional[str] | Omit = omit, + draft_answer: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> RemediationCreateResponse: """ Create Remediation Route @@ -114,7 +114,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, ) -> RemediationRetrieveResponse: """ Get Remediation Route @@ -144,22 +144,22 @@ def list( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - last_edited_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - last_edited_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - last_edited_by: Optional[str] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | NotGiven = NOT_GIVEN, - status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_at_end: Union[str, datetime, None] | Omit = omit, + last_edited_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_by: Optional[str] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | Omit = omit, + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | 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, ) -> SyncOffsetPageRemediations[RemediationListResponse]: """ List remediations by project ID. @@ -224,7 +224,7 @@ def delete( 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, ) -> None: """ Delete Remediation Route @@ -262,7 +262,7 @@ def edit_answer( 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, ) -> RemediationEditAnswerResponse: """ Edit Answer Route @@ -300,7 +300,7 @@ def edit_draft_answer( 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, ) -> RemediationEditDraftAnswerResponse: """ Edit Draft Answer Route @@ -339,7 +339,7 @@ def get_resolved_logs_count( 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, ) -> RemediationGetResolvedLogsCountResponse: """ Get Remediation With Resolved Logs Count Route @@ -375,7 +375,7 @@ def list_resolved_logs( 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, ) -> RemediationListResolvedLogsResponse: """ List resolved logs by remediation ID. @@ -411,7 +411,7 @@ def pause( 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, ) -> RemediationPauseResponse: """ Pause Remediation Route @@ -447,7 +447,7 @@ def publish( 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, ) -> RemediationPublishResponse: """ Publish Remediation Route @@ -483,7 +483,7 @@ def unpause( 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, ) -> RemediationUnpauseResponse: """ Unpause Remediation Route @@ -535,14 +535,14 @@ async def create( project_id: str, *, question: str, - answer: Optional[str] | NotGiven = NOT_GIVEN, - draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + answer: Optional[str] | Omit = omit, + draft_answer: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> RemediationCreateResponse: """ Create Remediation Route @@ -584,7 +584,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, ) -> RemediationRetrieveResponse: """ Get Remediation Route @@ -614,22 +614,22 @@ def list( self, project_id: str, *, - created_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - created_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - last_edited_at_end: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - last_edited_at_start: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - last_edited_by: Optional[str] | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | NotGiven = NOT_GIVEN, - status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | NotGiven = NOT_GIVEN, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_at_end: Union[str, datetime, None] | Omit = omit, + last_edited_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_by: Optional[str] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | Omit = omit, + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[RemediationListResponse, AsyncOffsetPageRemediations[RemediationListResponse]]: """ List remediations by project ID. @@ -694,7 +694,7 @@ async def delete( 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, ) -> None: """ Delete Remediation Route @@ -732,7 +732,7 @@ async def edit_answer( 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, ) -> RemediationEditAnswerResponse: """ Edit Answer Route @@ -772,7 +772,7 @@ async def edit_draft_answer( 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, ) -> RemediationEditDraftAnswerResponse: """ Edit Draft Answer Route @@ -811,7 +811,7 @@ async def get_resolved_logs_count( 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, ) -> RemediationGetResolvedLogsCountResponse: """ Get Remediation With Resolved Logs Count Route @@ -847,7 +847,7 @@ async def list_resolved_logs( 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, ) -> RemediationListResolvedLogsResponse: """ List resolved logs by remediation ID. @@ -883,7 +883,7 @@ async def pause( 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, ) -> RemediationPauseResponse: """ Pause Remediation Route @@ -919,7 +919,7 @@ async def publish( 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, ) -> RemediationPublishResponse: """ Publish Remediation Route @@ -955,7 +955,7 @@ async def unpause( 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, ) -> RemediationUnpauseResponse: """ Unpause Remediation Route diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py index 72f1502b..d8a20253 100644 --- a/src/codex/resources/users/myself/api_key.py +++ b/src/codex/resources/users/myself/api_key.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 ( @@ -48,7 +48,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, ) -> UserSchemaPublic: """Get user when authenticated with API key.""" return self._get( @@ -67,7 +67,7 @@ def refresh( 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, ) -> UserSchema: """Refresh the API key for an authenticated user""" return self._post( @@ -107,7 +107,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, ) -> UserSchemaPublic: """Get user when authenticated with API key.""" return await self._get( @@ -126,7 +126,7 @@ async def refresh( 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, ) -> UserSchema: """Refresh the API key for an authenticated user""" return await self._post( diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py index 3ee27229..ece8b611 100644 --- a/src/codex/resources/users/myself/myself.py +++ b/src/codex/resources/users/myself/myself.py @@ -12,7 +12,7 @@ APIKeyResourceWithStreamingResponse, AsyncAPIKeyResourceWithStreamingResponse, ) -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 ( @@ -71,7 +71,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, ) -> UserSchemaPublic: """Get user info for frontend.""" return self._get( @@ -119,7 +119,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, ) -> UserSchemaPublic: """Get user info for frontend.""" return await self._get( diff --git a/src/codex/resources/users/myself/organizations.py b/src/codex/resources/users/myself/organizations.py index 2d5b7127..ca95590d 100644 --- a/src/codex/resources/users/myself/organizations.py +++ b/src/codex/resources/users/myself/organizations.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 ( @@ -47,7 +47,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, ) -> UserOrganizationsSchema: """Get the organizations for an authenticated user""" return self._get( @@ -87,7 +87,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, ) -> UserOrganizationsSchema: """Get the organizations for an authenticated user""" return await self._get( diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index d207a96d..2d2dfaef 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -8,7 +8,7 @@ import httpx from ...types import user_activate_account_params -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -73,17 +73,17 @@ def activate_account( *, first_name: str, last_name: str, - account_activated_at: Union[str, datetime] | NotGiven = NOT_GIVEN, - discovery_source: Optional[str] | NotGiven = NOT_GIVEN, - is_account_activated: bool | NotGiven = NOT_GIVEN, - phone_number: Optional[str] | NotGiven = NOT_GIVEN, - user_provided_company_name: Optional[str] | NotGiven = NOT_GIVEN, + account_activated_at: Union[str, datetime] | Omit = omit, + discovery_source: Optional[str] | Omit = omit, + is_account_activated: bool | Omit = omit, + phone_number: Optional[str] | Omit = omit, + user_provided_company_name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserSchemaPublic: """ Activate an authenticated user's account @@ -151,17 +151,17 @@ async def activate_account( *, first_name: str, last_name: str, - account_activated_at: Union[str, datetime] | NotGiven = NOT_GIVEN, - discovery_source: Optional[str] | NotGiven = NOT_GIVEN, - is_account_activated: bool | NotGiven = NOT_GIVEN, - phone_number: Optional[str] | NotGiven = NOT_GIVEN, - user_provided_company_name: Optional[str] | NotGiven = NOT_GIVEN, + account_activated_at: Union[str, datetime] | Omit = omit, + discovery_source: Optional[str] | Omit = omit, + is_account_activated: bool | Omit = omit, + phone_number: Optional[str] | Omit = omit, + user_provided_company_name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UserSchemaPublic: """ Activate an authenticated user's account diff --git a/src/codex/resources/users/verification.py b/src/codex/resources/users/verification.py index e75326e1..d4ef02d9 100644 --- a/src/codex/resources/users/verification.py +++ b/src/codex/resources/users/verification.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 ( @@ -47,7 +47,7 @@ def resend( 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, ) -> VerificationResendResponse: """Resend verification email to the specified user through Auth0.""" return self._post( @@ -87,7 +87,7 @@ async def resend( 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, ) -> VerificationResendResponse: """Resend verification email to the specified user through Auth0.""" return await self._post( diff --git a/tests/test_transform.py b/tests/test_transform.py index 4067f583..9afdf358 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from codex._types import NOT_GIVEN, Base64FileInput +from codex._types import Base64FileInput, omit, not_given from codex._utils import ( PropertyInfo, transform as _transform, @@ -450,4 +450,11 @@ async def test_transform_skipping(use_async: bool) -> None: @pytest.mark.asyncio async def test_strips_notgiven(use_async: bool) -> None: assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} - assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {} From 077a99c21997b6fbf8c6723e6dd894c414618273 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 21:05:11 +0000 Subject: [PATCH 256/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index c07ebda0..feb65cbf 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 openapi_spec_hash: f263c6c6d8d75a8f7c1e9c65188e7ef2 -config_hash: 6c3ad84d97bf1d0989ad2ec0cae64078 +config_hash: 04312af86542d1127f09d3f3cbe5bb50 From 14bce5af5f68d261722ef5903b26acbec9634957 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 21:07:27 +0000 Subject: [PATCH 257/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 315f7d30..7657c56b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.26" + ".": "0.1.0-alpha.27" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 70e7e284..8288a774 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.26" +version = "0.1.0-alpha.27" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 1e7d5f15..0e64acc3 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.26" # x-release-please-version +__version__ = "0.1.0-alpha.27" # x-release-please-version From 096ce06f00b1c7d090d4937f5a371cbd084588bb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 22:31:11 +0000 Subject: [PATCH 258/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/evals.py | 30 +++++++++++++ src/codex/types/project_create_params.py | 36 +++++++++++++++ src/codex/types/project_list_response.py | 44 +++++++++++++++++++ src/codex/types/project_retrieve_response.py | 36 +++++++++++++++ src/codex/types/project_return_schema.py | 36 +++++++++++++++ src/codex/types/project_update_params.py | 36 +++++++++++++++ src/codex/types/project_validate_response.py | 4 ++ .../types/projects/eval_create_params.py | 6 +++ .../types/projects/eval_list_response.py | 6 +++ .../types/projects/eval_update_params.py | 12 +++++ .../query_log_list_by_group_response.py | 2 + .../query_log_list_groups_response.py | 2 + .../types/projects/query_log_list_response.py | 2 + .../projects/query_log_retrieve_response.py | 2 + ...remediation_list_resolved_logs_response.py | 2 + tests/api_resources/projects/test_evals.py | 6 +++ tests/api_resources/test_projects.py | 24 ++++++++++ 18 files changed, 287 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index feb65cbf..5b7840a4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: f263c6c6d8d75a8f7c1e9c65188e7ef2 +openapi_spec_hash: 43ecb34eaf8efd3fe94b23f2c859fe05 config_hash: 04312af86542d1127f09d3f3cbe5bb50 diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py index 54802918..f732981f 100644 --- a/src/codex/resources/projects/evals.py +++ b/src/codex/resources/projects/evals.py @@ -54,6 +54,7 @@ def create( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -86,6 +87,9 @@ def create( enabled: Allows the evaluation to be disabled without removing it + guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be + guardrailed + is_default: Whether the eval is a default, built-in eval or a custom eval priority: Priority order for evals (lower number = higher priority) to determine primary @@ -124,6 +128,7 @@ def create( "name": name, "context_identifier": context_identifier, "enabled": enabled, + "guardrailed_fallback_message": guardrailed_fallback_message, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, @@ -152,6 +157,7 @@ def update( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -184,6 +190,9 @@ def update( enabled: Allows the evaluation to be disabled without removing it + guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be + guardrailed + is_default: Whether the eval is a default, built-in eval or a custom eval priority: Priority order for evals (lower number = higher priority) to determine primary @@ -221,6 +230,7 @@ def update( project_id: str, body_eval_key: str, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, priority: Optional[int] | Omit = omit, should_escalate: bool | Omit = omit, should_guardrail: bool | Omit = omit, @@ -242,6 +252,9 @@ def update( enabled: Allows the evaluation to be disabled without removing it + guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be + guardrailed + priority: Priority order for evals (lower number = higher priority) to determine primary eval issue to surface @@ -275,6 +288,7 @@ def update( name: str | Omit = omit, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -303,6 +317,7 @@ def update( "name": name, "context_identifier": context_identifier, "enabled": enabled, + "guardrailed_fallback_message": guardrailed_fallback_message, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, @@ -433,6 +448,7 @@ async def create( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -465,6 +481,9 @@ async def create( enabled: Allows the evaluation to be disabled without removing it + guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be + guardrailed + is_default: Whether the eval is a default, built-in eval or a custom eval priority: Priority order for evals (lower number = higher priority) to determine primary @@ -503,6 +522,7 @@ async def create( "name": name, "context_identifier": context_identifier, "enabled": enabled, + "guardrailed_fallback_message": guardrailed_fallback_message, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, @@ -531,6 +551,7 @@ async def update( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -563,6 +584,9 @@ async def update( enabled: Allows the evaluation to be disabled without removing it + guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be + guardrailed + is_default: Whether the eval is a default, built-in eval or a custom eval priority: Priority order for evals (lower number = higher priority) to determine primary @@ -600,6 +624,7 @@ async def update( project_id: str, body_eval_key: str, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, priority: Optional[int] | Omit = omit, should_escalate: bool | Omit = omit, should_guardrail: bool | Omit = omit, @@ -621,6 +646,9 @@ async def update( enabled: Allows the evaluation to be disabled without removing it + guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be + guardrailed + priority: Priority order for evals (lower number = higher priority) to determine primary eval issue to surface @@ -654,6 +682,7 @@ async def update( name: str | Omit = omit, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, + guardrailed_fallback_message: Optional[str] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -682,6 +711,7 @@ async def update( "name": name, "context_identifier": context_identifier, "enabled": enabled, + "guardrailed_fallback_message": guardrailed_fallback_message, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index 0d4a4f57..c17be679 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -57,6 +57,12 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -110,6 +116,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -145,6 +157,12 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -180,6 +198,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -215,6 +239,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -250,6 +280,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index db1666cf..9fd609f5 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -19,6 +19,7 @@ "ProjectConfigEvalConfigDefaultEvalsResponseGroundedness", "ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness", "ProjectConfigEvalConfigDefaultEvalsTrustworthiness", + "Filters", ] @@ -47,6 +48,12 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" @@ -100,6 +107,12 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -135,6 +148,12 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -170,6 +189,12 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -205,6 +230,12 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -240,6 +271,12 @@ class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -354,7 +391,14 @@ class Project(BaseModel): unaddressed_count: Optional[int] = None +class Filters(BaseModel): + query: Optional[str] = None + + class ProjectListResponse(BaseModel): projects: List[Project] total_count: int + + filters: Optional[Filters] = None + """Applied filters for the projects list request""" diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 19abb67f..949ce254 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -46,6 +46,12 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" @@ -99,6 +105,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -134,6 +146,12 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -169,6 +187,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -204,6 +228,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -239,6 +269,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index cb114db9..cf2f3a89 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -46,6 +46,12 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" @@ -99,6 +105,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -134,6 +146,12 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -169,6 +187,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -204,6 +228,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary @@ -239,6 +269,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] = None """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 9b32bb6d..4b4de76c 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -55,6 +55,12 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -108,6 +114,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -143,6 +155,12 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -178,6 +196,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -213,6 +237,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary @@ -248,6 +278,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index e56d3bca..1c0cc4a2 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -12,10 +12,14 @@ class DeterministicGuardrailsResults(BaseModel): should_guardrail: bool + fallback_message: Optional[str] = None + matches: Optional[List[str]] = None class EvalScores(BaseModel): + guardrailed_fallback_message: Optional[str] = None + score: Optional[float] = None triggered: bool diff --git a/src/codex/types/projects/eval_create_params.py b/src/codex/types/projects/eval_create_params.py index aead432e..5f66f6ad 100644 --- a/src/codex/types/projects/eval_create_params.py +++ b/src/codex/types/projects/eval_create_params.py @@ -33,6 +33,12 @@ class EvalCreateParams(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py index eb2cb9a3..073312d8 100644 --- a/src/codex/types/projects/eval_list_response.py +++ b/src/codex/types/projects/eval_list_response.py @@ -33,6 +33,12 @@ class Eval(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] = None + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" diff --git a/src/codex/types/projects/eval_update_params.py b/src/codex/types/projects/eval_update_params.py index 545c0b29..1cfa8360 100644 --- a/src/codex/types/projects/eval_update_params.py +++ b/src/codex/types/projects/eval_update_params.py @@ -37,6 +37,12 @@ class CustomEvalCreateOrUpdateSchema(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -85,6 +91,12 @@ class DefaultEvalUpdateSchema(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + priority: Optional[int] """ Priority order for evals (lower number = higher priority) to determine primary diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index f6156eb2..fc2e9043 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -95,6 +95,8 @@ class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): should_guardrail: bool + fallback_message: Optional[str] = None + matches: Optional[List[str]] = None diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 11c24b21..e61b6512 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -92,6 +92,8 @@ class DeterministicGuardrailsResults(BaseModel): should_guardrail: bool + fallback_message: Optional[str] = None + matches: Optional[List[str]] = None diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 098ac817..6494d83f 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -92,6 +92,8 @@ class DeterministicGuardrailsResults(BaseModel): should_guardrail: bool + fallback_message: Optional[str] = None + matches: Optional[List[str]] = None diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index cae996c6..abfaeebb 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -92,6 +92,8 @@ class DeterministicGuardrailsResults(BaseModel): should_guardrail: bool + fallback_message: Optional[str] = None + matches: Optional[List[str]] = None diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 1ef0e6f0..e2239c41 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -93,6 +93,8 @@ class QueryLogDeterministicGuardrailsResults(BaseModel): should_guardrail: bool + fallback_message: Optional[str] = None + matches: Optional[List[str]] = None diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py index 7266751d..1ccde4f3 100644 --- a/tests/api_resources/projects/test_evals.py +++ b/tests/api_resources/projects/test_evals.py @@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: name="name", context_identifier="context_identifier", enabled=True, + guardrailed_fallback_message="guardrailed_fallback_message", is_default=True, priority=0, query_identifier="query_identifier", @@ -116,6 +117,7 @@ def test_method_update_with_all_params_overload_1(self, client: Codex) -> None: name="name", context_identifier="context_identifier", enabled=True, + guardrailed_fallback_message="guardrailed_fallback_message", is_default=True, priority=0, query_identifier="query_identifier", @@ -200,6 +202,7 @@ def test_method_update_with_all_params_overload_2(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", body_eval_key="eval_key", enabled=True, + guardrailed_fallback_message="guardrailed_fallback_message", priority=0, should_escalate=True, should_guardrail=True, @@ -387,6 +390,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> name="name", context_identifier="context_identifier", enabled=True, + guardrailed_fallback_message="guardrailed_fallback_message", is_default=True, priority=0, query_identifier="query_identifier", @@ -464,6 +468,7 @@ async def test_method_update_with_all_params_overload_1(self, async_client: Asyn name="name", context_identifier="context_identifier", enabled=True, + guardrailed_fallback_message="guardrailed_fallback_message", is_default=True, priority=0, query_identifier="query_identifier", @@ -548,6 +553,7 @@ async def test_method_update_with_all_params_overload_2(self, async_client: Asyn project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", body_eval_key="eval_key", enabled=True, + guardrailed_fallback_message="guardrailed_fallback_message", priority=0, should_escalate=True, should_guardrail=True, diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 153c3aae..84ad6cdc 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -49,6 +49,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "name": "name", "context_identifier": "context_identifier", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -65,6 +66,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -75,6 +77,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -85,6 +88,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -95,6 +99,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -105,6 +110,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -227,6 +233,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "name": "name", "context_identifier": "context_identifier", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -243,6 +250,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -253,6 +261,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -263,6 +272,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -273,6 +283,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -283,6 +294,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -728,6 +740,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "name": "name", "context_identifier": "context_identifier", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -744,6 +757,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -754,6 +768,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -764,6 +779,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -774,6 +790,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -784,6 +801,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -906,6 +924,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "name": "name", "context_identifier": "context_identifier", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -922,6 +941,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -932,6 +952,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -942,6 +963,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -952,6 +974,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -962,6 +985,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", "priority": 0, "should_escalate": True, "should_guardrail": True, From 251ec7e0ea5cdeb500f520f917dbda977500a7fc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 22:33:19 +0000 Subject: [PATCH 259/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7657c56b..f4710698 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.27" + ".": "0.1.0-alpha.28" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8288a774..4a249aac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.27" +version = "0.1.0-alpha.28" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 0e64acc3..82d84e82 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.27" # x-release-please-version +__version__ = "0.1.0-alpha.28" # x-release-please-version From a4b946e54a459959f1225363a4c335ec41b1bd1e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 02:25:26 +0000 Subject: [PATCH 260/320] chore: do not install brew dependencies in ./scripts/bootstrap by default --- scripts/bootstrap | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/bootstrap b/scripts/bootstrap index e84fe62c..b430fee3 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi From 0948091ce2fcf1781be4608a45a671b38385b4d0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 01:17:04 +0000 Subject: [PATCH 261/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 5b7840a4..c142a641 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 43ecb34eaf8efd3fe94b23f2c859fe05 +openapi_spec_hash: ccd43715e744170e8b682a0889b14f65 config_hash: 04312af86542d1127f09d3f3cbe5bb50 From febd2d9b747ee751506130506781a8ddeff24f1a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 21:18:09 +0000 Subject: [PATCH 262/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index c142a641..5ba8cf59 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: ccd43715e744170e8b682a0889b14f65 +openapi_spec_hash: 789846f4e9e0c710cd7e2228e73af8d7 config_hash: 04312af86542d1127f09d3f3cbe5bb50 From 3a7dc3c007f32d66463adca4f173145f99dc82b2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:18:26 +0000 Subject: [PATCH 263/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 5ba8cf59..c00b8d1b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 54 -openapi_spec_hash: 789846f4e9e0c710cd7e2228e73af8d7 +openapi_spec_hash: b54b36ebcaf88c1ddb6d51d24da75420 config_hash: 04312af86542d1127f09d3f3cbe5bb50 From 74f279fd99b81fc4c3aa2a1e5dcdd10a963ece1f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:17:36 +0000 Subject: [PATCH 264/320] feat(api): add /detect to accessible routes --- .stats.yml | 4 +- api.md | 2 + src/codex/resources/projects/projects.py | 344 +++++++ src/codex/types/__init__.py | 2 + src/codex/types/project_detect_params.py | 992 +++++++++++++++++++++ src/codex/types/project_detect_response.py | 66 ++ tests/api_resources/test_projects.py | 393 ++++++++ 7 files changed, 1801 insertions(+), 2 deletions(-) create mode 100644 src/codex/types/project_detect_params.py create mode 100644 src/codex/types/project_detect_response.py diff --git a/.stats.yml b/.stats.yml index c00b8d1b..4739e919 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 54 +configured_endpoints: 55 openapi_spec_hash: b54b36ebcaf88c1ddb6d51d24da75420 -config_hash: 04312af86542d1127f09d3f3cbe5bb50 +config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/api.md b/api.md index dc60687a..5d40e153 100644 --- a/api.md +++ b/api.md @@ -139,6 +139,7 @@ from codex.types import ( ProjectReturnSchema, ProjectRetrieveResponse, ProjectListResponse, + ProjectDetectResponse, ProjectInviteSmeResponse, ProjectRetrieveAnalyticsResponse, ProjectValidateResponse, @@ -152,6 +153,7 @@ Methods: - client.projects.update(project_id, \*\*params) -> ProjectReturnSchema - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None +- client.projects.detect(project_id, \*\*params) -> ProjectDetectResponse - client.projects.export(project_id) -> object - client.projects.invite_sme(project_id, \*\*params) -> ProjectInviteSmeResponse - client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 2ed4dd2c..4575e8a2 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -18,6 +18,7 @@ from ...types import ( project_list_params, project_create_params, + project_detect_params, project_update_params, project_validate_params, project_invite_sme_params, @@ -60,6 +61,7 @@ from ..._base_client import make_request_options from ...types.project_list_response import ProjectListResponse from ...types.project_return_schema import ProjectReturnSchema +from ...types.project_detect_response import ProjectDetectResponse from ...types.project_retrieve_response import ProjectRetrieveResponse from ...types.project_validate_response import ProjectValidateResponse from ...types.project_invite_sme_response import ProjectInviteSmeResponse @@ -314,6 +316,171 @@ def delete( cast_to=NoneType, ) + def detect( + self, + project_id: str, + *, + context: str, + query: str, + response: project_detect_params.Response, + constrain_outputs: Optional[SequenceNotStr[str]] | Omit = omit, + eval_config: project_detect_params.EvalConfig | Omit = omit, + messages: Iterable[project_detect_params.Message] | Omit = omit, + options: Optional[project_detect_params.Options] | Omit = omit, + quality_preset: Literal["best", "high", "medium", "low", "base"] | Omit = omit, + rewritten_question: Optional[str] | Omit = omit, + task: Optional[str] | Omit = omit, + tools: Optional[Iterable[project_detect_params.Tool]] | 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, + ) -> ProjectDetectResponse: + """ + Detect whether a response, given the provided query and context, is potentially + bad. No query is logged in the project for this API route. Optionally, users can + add custom evals for each request, or swap in different settings for the current + project's evals. + + Args: + eval_config: All of the evals that should be used for this query + + messages: Message history to provide conversation context for the query. Messages contain + up to and including the latest user prompt to the LLM. + + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + This parameter has no effect when `disable_trustworthiness` is True. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include - "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. + + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. + + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + + rewritten_question: The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + + tools: Tools to use for the LLM call. If not provided, it is assumed no tools were + provided to the LLM. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/detect", + body=maybe_transform( + { + "context": context, + "query": query, + "response": response, + "constrain_outputs": constrain_outputs, + "eval_config": eval_config, + "messages": messages, + "options": options, + "quality_preset": quality_preset, + "rewritten_question": rewritten_question, + "task": task, + "tools": tools, + }, + project_detect_params.ProjectDetectParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectDetectResponse, + ) + def export( self, project_id: str, @@ -886,6 +1053,171 @@ async def delete( cast_to=NoneType, ) + async def detect( + self, + project_id: str, + *, + context: str, + query: str, + response: project_detect_params.Response, + constrain_outputs: Optional[SequenceNotStr[str]] | Omit = omit, + eval_config: project_detect_params.EvalConfig | Omit = omit, + messages: Iterable[project_detect_params.Message] | Omit = omit, + options: Optional[project_detect_params.Options] | Omit = omit, + quality_preset: Literal["best", "high", "medium", "low", "base"] | Omit = omit, + rewritten_question: Optional[str] | Omit = omit, + task: Optional[str] | Omit = omit, + tools: Optional[Iterable[project_detect_params.Tool]] | 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, + ) -> ProjectDetectResponse: + """ + Detect whether a response, given the provided query and context, is potentially + bad. No query is logged in the project for this API route. Optionally, users can + add custom evals for each request, or swap in different settings for the current + project's evals. + + Args: + eval_config: All of the evals that should be used for this query + + messages: Message history to provide conversation context for the query. Messages contain + up to and including the latest user prompt to the LLM. + + options: Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + This parameter has no effect when `disable_trustworthiness` is True. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include - "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. + + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. + + quality_preset: The quality preset to use for the TLM or Trustworthy RAG API. + + rewritten_question: The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + + tools: Tools to use for the LLM call. If not provided, it is assumed no tools were + provided to the LLM. + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/detect", + body=await async_maybe_transform( + { + "context": context, + "query": query, + "response": response, + "constrain_outputs": constrain_outputs, + "eval_config": eval_config, + "messages": messages, + "options": options, + "quality_preset": quality_preset, + "rewritten_question": rewritten_question, + "task": task, + "tools": tools, + }, + project_detect_params.ProjectDetectParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProjectDetectResponse, + ) + async def export( self, project_id: str, @@ -1231,6 +1563,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.delete = to_raw_response_wrapper( projects.delete, ) + self.detect = to_raw_response_wrapper( + projects.detect, + ) self.export = to_raw_response_wrapper( projects.export, ) @@ -1280,6 +1615,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.delete = async_to_raw_response_wrapper( projects.delete, ) + self.detect = async_to_raw_response_wrapper( + projects.detect, + ) self.export = async_to_raw_response_wrapper( projects.export, ) @@ -1329,6 +1667,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.delete = to_streamed_response_wrapper( projects.delete, ) + self.detect = to_streamed_response_wrapper( + projects.detect, + ) self.export = to_streamed_response_wrapper( projects.export, ) @@ -1378,6 +1719,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.delete = async_to_streamed_response_wrapper( projects.delete, ) + self.detect = async_to_streamed_response_wrapper( + projects.detect, + ) self.export = async_to_streamed_response_wrapper( projects.export, ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 322b513b..ca9129a7 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -5,9 +5,11 @@ from .project_list_params import ProjectListParams as ProjectListParams from .health_check_response import HealthCheckResponse as HealthCheckResponse from .project_create_params import ProjectCreateParams as ProjectCreateParams +from .project_detect_params import ProjectDetectParams as ProjectDetectParams from .project_list_response import ProjectListResponse as ProjectListResponse from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema from .project_update_params import ProjectUpdateParams as ProjectUpdateParams +from .project_detect_response import ProjectDetectResponse as ProjectDetectResponse from .project_validate_params import ProjectValidateParams as ProjectValidateParams from .project_invite_sme_params import ProjectInviteSmeParams as ProjectInviteSmeParams from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse diff --git a/src/codex/types/project_detect_params.py b/src/codex/types/project_detect_params.py new file mode 100644 index 00000000..f29d3e00 --- /dev/null +++ b/src/codex/types/project_detect_params.py @@ -0,0 +1,992 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import builtins +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr + +__all__ = [ + "ProjectDetectParams", + "Response", + "ResponseChatCompletion", + "ResponseChatCompletionChoice", + "ResponseChatCompletionChoiceMessage", + "ResponseChatCompletionChoiceMessageAnnotation", + "ResponseChatCompletionChoiceMessageAnnotationURLCitation", + "ResponseChatCompletionChoiceMessageAudio", + "ResponseChatCompletionChoiceMessageFunctionCall", + "ResponseChatCompletionChoiceMessageToolCall", + "ResponseChatCompletionChoiceMessageToolCallFunction", + "ResponseChatCompletionChoiceLogprobs", + "ResponseChatCompletionChoiceLogprobsContent", + "ResponseChatCompletionChoiceLogprobsContentTopLogprob", + "ResponseChatCompletionChoiceLogprobsRefusal", + "ResponseChatCompletionChoiceLogprobsRefusalTopLogprob", + "ResponseChatCompletionUsage", + "ResponseChatCompletionUsageCompletionTokensDetails", + "ResponseChatCompletionUsagePromptTokensDetails", + "EvalConfig", + "EvalConfigCustomEvals", + "EvalConfigCustomEvalsEvals", + "EvalConfigDefaultEvals", + "EvalConfigDefaultEvalsContextSufficiency", + "EvalConfigDefaultEvalsQueryEase", + "EvalConfigDefaultEvalsResponseGroundedness", + "EvalConfigDefaultEvalsResponseHelpfulness", + "EvalConfigDefaultEvalsTrustworthiness", + "Message", + "MessageChatCompletionAssistantMessageParamInput", + "MessageChatCompletionAssistantMessageParamInputAudio", + "MessageChatCompletionAssistantMessageParamInputContentUnionMember1", + "MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam", + "MessageChatCompletionAssistantMessageParamInputFunctionCall", + "MessageChatCompletionAssistantMessageParamInputToolCall", + "MessageChatCompletionAssistantMessageParamInputToolCallFunction", + "MessageChatCompletionToolMessageParam", + "MessageChatCompletionToolMessageParamContentUnionMember1", + "MessageChatCompletionUserMessageParamInput", + "MessageChatCompletionUserMessageParamInputContentUnionMember1", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam", + "MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio", + "MessageChatCompletionUserMessageParamInputContentUnionMember1File", + "MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile", + "MessageChatCompletionSystemMessageParam", + "MessageChatCompletionSystemMessageParamContentUnionMember1", + "MessageChatCompletionFunctionMessageParam", + "MessageChatCompletionDeveloperMessageParam", + "MessageChatCompletionDeveloperMessageParamContentUnionMember1", + "Options", + "Tool", + "ToolFunction", +] + + +class ProjectDetectParams(TypedDict, total=False): + context: Required[str] + + query: Required[str] + + response: Required[Response] + + constrain_outputs: Optional[SequenceNotStr[str]] + + eval_config: EvalConfig + """All of the evals that should be used for this query""" + + messages: Iterable[Message] + """Message history to provide conversation context for the query. + + Messages contain up to and including the latest user prompt to the LLM. + """ + + options: Optional[Options] + """ + Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the + quality preset. + + For all options described below, higher settings will lead to longer runtimes + and may consume more tokens internally. You may not be able to run long prompts + (or prompts with long responses) in your account, unless your token/rate limits + are increased. If you hit token limit issues, try lower/less expensive + TLMOptions to be able to run longer prompts/responses, or contact Cleanlab to + increase your limits. + + The default values corresponding to each quality preset are: + + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, + `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, + `reasoning_effort` = `"none"`. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base + `model`, and `max_tokens` is set to 512. You can set custom values for these + arguments regardless of the quality preset specified. + + Args: model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", + "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", + "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", + "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", + "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", + "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use + (better models yield better results, faster models yield faster results). - + Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", + "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", + "claude-3.5-haiku". - Recommended models for accuracy: "gpt-5", "gpt-4.1", + "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". - Recommended models + for low latency/costs: "gpt-4.1-nano", "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + This parameter has no effect when `disable_trustworthiness` is True. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include - "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. + + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. + """ + + quality_preset: Literal["best", "high", "medium", "low", "base"] + """The quality preset to use for the TLM or Trustworthy RAG API.""" + + rewritten_question: Optional[str] + """ + The re-written query if it was provided by the client to Codex from a user to be + used instead of the original query. + """ + + task: Optional[str] + + tools: Optional[Iterable[Tool]] + """Tools to use for the LLM call. + + If not provided, it is assumed no tools were provided to the LLM. + """ + + +class ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped(TypedDict, total=False): + end_index: Required[int] + + start_index: Required[int] + + title: Required[str] + + url: Required[str] + + +ResponseChatCompletionChoiceMessageAnnotationURLCitation: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAnnotationURLCitationTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageAnnotationTyped(TypedDict, total=False): + type: Required[Literal["url_citation"]] + + url_citation: Required[ResponseChatCompletionChoiceMessageAnnotationURLCitation] + + +ResponseChatCompletionChoiceMessageAnnotation: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAnnotationTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageAudioTyped(TypedDict, total=False): + id: Required[str] + + data: Required[str] + + expires_at: Required[int] + + transcript: Required[str] + + +ResponseChatCompletionChoiceMessageAudio: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageAudioTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageFunctionCallTyped(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +ResponseChatCompletionChoiceMessageFunctionCall: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageFunctionCallTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageToolCallFunctionTyped(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +ResponseChatCompletionChoiceMessageToolCallFunction: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageToolCallFunctionTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageToolCallTyped(TypedDict, total=False): + id: Required[str] + + function: Required[ResponseChatCompletionChoiceMessageToolCallFunction] + + type: Required[Literal["function"]] + + +ResponseChatCompletionChoiceMessageToolCall: TypeAlias = Union[ + ResponseChatCompletionChoiceMessageToolCallTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceMessageTyped(TypedDict, total=False): + role: Required[Literal["assistant"]] + + annotations: Optional[Iterable[ResponseChatCompletionChoiceMessageAnnotation]] + + audio: Optional[ResponseChatCompletionChoiceMessageAudio] + + content: Optional[str] + + function_call: Optional[ResponseChatCompletionChoiceMessageFunctionCall] + + refusal: Optional[str] + + tool_calls: Optional[Iterable[ResponseChatCompletionChoiceMessageToolCall]] + + +ResponseChatCompletionChoiceMessage: TypeAlias = Union[ResponseChatCompletionChoiceMessageTyped, Dict[str, object]] + + +class ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsContentTopLogprob: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsContentTopLogprobTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsContentTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsContentTopLogprob]] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsContent: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsContentTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsRefusalTopLogprob: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsRefusalTopLogprobTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsRefusalTyped(TypedDict, total=False): + token: Required[str] + + logprob: Required[float] + + top_logprobs: Required[Iterable[ResponseChatCompletionChoiceLogprobsRefusalTopLogprob]] + + bytes: Optional[Iterable[int]] + + +ResponseChatCompletionChoiceLogprobsRefusal: TypeAlias = Union[ + ResponseChatCompletionChoiceLogprobsRefusalTyped, Dict[str, object] +] + + +class ResponseChatCompletionChoiceLogprobsTyped(TypedDict, total=False): + content: Optional[Iterable[ResponseChatCompletionChoiceLogprobsContent]] + + refusal: Optional[Iterable[ResponseChatCompletionChoiceLogprobsRefusal]] + + +ResponseChatCompletionChoiceLogprobs: TypeAlias = Union[ResponseChatCompletionChoiceLogprobsTyped, Dict[str, object]] + + +class ResponseChatCompletionChoiceTyped(TypedDict, total=False): + finish_reason: Required[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] + + index: Required[int] + + message: Required[ResponseChatCompletionChoiceMessage] + + logprobs: Optional[ResponseChatCompletionChoiceLogprobs] + + +ResponseChatCompletionChoice: TypeAlias = Union[ResponseChatCompletionChoiceTyped, Dict[str, object]] + + +class ResponseChatCompletionUsageCompletionTokensDetailsTyped(TypedDict, total=False): + accepted_prediction_tokens: Optional[int] + + audio_tokens: Optional[int] + + reasoning_tokens: Optional[int] + + rejected_prediction_tokens: Optional[int] + + +ResponseChatCompletionUsageCompletionTokensDetails: TypeAlias = Union[ + ResponseChatCompletionUsageCompletionTokensDetailsTyped, Dict[str, object] +] + + +class ResponseChatCompletionUsagePromptTokensDetailsTyped(TypedDict, total=False): + audio_tokens: Optional[int] + + cached_tokens: Optional[int] + + +ResponseChatCompletionUsagePromptTokensDetails: TypeAlias = Union[ + ResponseChatCompletionUsagePromptTokensDetailsTyped, Dict[str, object] +] + + +class ResponseChatCompletionUsageTyped(TypedDict, total=False): + completion_tokens: Required[int] + + prompt_tokens: Required[int] + + total_tokens: Required[int] + + completion_tokens_details: Optional[ResponseChatCompletionUsageCompletionTokensDetails] + + prompt_tokens_details: Optional[ResponseChatCompletionUsagePromptTokensDetails] + + +ResponseChatCompletionUsage: TypeAlias = Union[ResponseChatCompletionUsageTyped, Dict[str, object]] + + +class ResponseChatCompletionTyped(TypedDict, total=False): + id: Required[str] + + choices: Required[Iterable[ResponseChatCompletionChoice]] + + created: Required[int] + + model: Required[str] + + object: Required[Literal["chat.completion"]] + + service_tier: Optional[Literal["scale", "default"]] + + system_fingerprint: Optional[str] + + usage: Optional[ResponseChatCompletionUsage] + + +ResponseChatCompletion: TypeAlias = Union[ResponseChatCompletionTyped, Dict[str, builtins.object]] + +Response: TypeAlias = Union[str, ResponseChatCompletion] + + +class EvalConfigCustomEvalsEvals(TypedDict, total=False): + criteria: Required[str] + """ + The evaluation criteria text that describes what aspect is being evaluated and + how + """ + + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + context_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the retrieved + context. + """ + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + is_default: bool + """Whether the eval is a default, built-in eval or a custom eval""" + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + query_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the user's query. + """ + + response_identifier: Optional[str] + """ + The exact string used in your evaluation criteria to reference the RAG/LLM + response. + """ + + should_escalate: bool + """ + If true, failing this eval means the question should be escalated to Codex for + an SME to review + """ + + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class EvalConfigCustomEvals(TypedDict, total=False): + evals: Dict[str, EvalConfigCustomEvalsEvals] + + +class EvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the question should be escalated to Codex for + an SME to review + """ + + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class EvalConfigDefaultEvalsQueryEase(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the question should be escalated to Codex for + an SME to review + """ + + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class EvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the question should be escalated to Codex for + an SME to review + """ + + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class EvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the question should be escalated to Codex for + an SME to review + """ + + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class EvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): + eval_key: Required[str] + """ + Unique key for eval metric - currently maps to the TrustworthyRAG name property + and eval_scores dictionary key to check against threshold + """ + + name: Required[str] + """Display name/label for the evaluation metric""" + + enabled: bool + """Allows the evaluation to be disabled without removing it""" + + guardrailed_fallback_message: Optional[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Optional[int] + """ + Priority order for evals (lower number = higher priority) to determine primary + eval issue to surface + """ + + should_escalate: bool + """ + If true, failing this eval means the question should be escalated to Codex for + an SME to review + """ + + should_guardrail: bool + """If true, failing this eval means the response should be guardrailed""" + + threshold: float + """Threshold value that determines if the evaluation fails""" + + threshold_direction: Literal["above", "below"] + """Whether the evaluation fails when score is above or below the threshold""" + + +class EvalConfigDefaultEvals(TypedDict, total=False): + context_sufficiency: EvalConfigDefaultEvalsContextSufficiency + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + query_ease: EvalConfigDefaultEvalsQueryEase + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_groundedness: EvalConfigDefaultEvalsResponseGroundedness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + response_helpfulness: EvalConfigDefaultEvalsResponseHelpfulness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + trustworthiness: EvalConfigDefaultEvalsTrustworthiness + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, while + other properties like thresholds and priorities can be configured. + """ + + +class EvalConfig(TypedDict, total=False): + custom_evals: EvalConfigCustomEvals + """Configuration for custom evaluation metrics.""" + + default_evals: EvalConfigDefaultEvals + """Configuration for default evaluation metrics.""" + + +class MessageChatCompletionAssistantMessageParamInputAudio(TypedDict, total=False): + id: Required[str] + + +class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam( + TypedDict, total=False +): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam( + TypedDict, total=False +): + refusal: Required[str] + + type: Required[Literal["refusal"]] + + +MessageChatCompletionAssistantMessageParamInputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionAssistantMessageParamInputContentUnionMember1ChatCompletionContentPartRefusalParam, +] + + +class MessageChatCompletionAssistantMessageParamInputFunctionCall(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class MessageChatCompletionAssistantMessageParamInputToolCallFunction(TypedDict, total=False): + arguments: Required[str] + + name: Required[str] + + +class MessageChatCompletionAssistantMessageParamInputToolCall(TypedDict, total=False): + id: Required[str] + + function: Required[MessageChatCompletionAssistantMessageParamInputToolCallFunction] + + type: Required[Literal["function"]] + + +class MessageChatCompletionAssistantMessageParamInput(TypedDict, total=False): + role: Required[Literal["assistant"]] + + audio: Optional[MessageChatCompletionAssistantMessageParamInputAudio] + + content: Union[str, Iterable[MessageChatCompletionAssistantMessageParamInputContentUnionMember1], None] + + function_call: Optional[MessageChatCompletionAssistantMessageParamInputFunctionCall] + + name: str + + refusal: Optional[str] + + tool_calls: Iterable[MessageChatCompletionAssistantMessageParamInputToolCall] + + +class MessageChatCompletionToolMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionToolMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionToolMessageParamContentUnionMember1]]] + + role: Required[Literal["tool"]] + + tool_call_id: Required[str] + + +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam( + TypedDict, total=False +): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL( + TypedDict, total=False +): + url: Required[str] + + detail: Literal["auto", "low", "high"] + + +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam( + TypedDict, total=False +): + image_url: Required[ + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParamImageURL + ] + + type: Required[Literal["image_url"]] + + +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio( + TypedDict, total=False +): + data: Required[str] + + format: Required[Literal["wav", "mp3"]] + + +class MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam( + TypedDict, total=False +): + input_audio: Required[ + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParamInputAudio + ] + + type: Required[Literal["input_audio"]] + + +class MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile(TypedDict, total=False): + file_data: str + + file_id: str + + filename: str + + +class MessageChatCompletionUserMessageParamInputContentUnionMember1File(TypedDict, total=False): + file: Required[MessageChatCompletionUserMessageParamInputContentUnionMember1FileFile] + + type: Required[Literal["file"]] + + +MessageChatCompletionUserMessageParamInputContentUnionMember1: TypeAlias = Union[ + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartTextParam, + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartImageParam, + MessageChatCompletionUserMessageParamInputContentUnionMember1ChatCompletionContentPartInputAudioParam, + MessageChatCompletionUserMessageParamInputContentUnionMember1File, +] + + +class MessageChatCompletionUserMessageParamInput(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionUserMessageParamInputContentUnionMember1]]] + + role: Required[Literal["user"]] + + name: str + + +class MessageChatCompletionSystemMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionSystemMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionSystemMessageParamContentUnionMember1]]] + + role: Required[Literal["system"]] + + name: str + + +class MessageChatCompletionFunctionMessageParam(TypedDict, total=False): + content: Required[Optional[str]] + + name: Required[str] + + role: Required[Literal["function"]] + + +class MessageChatCompletionDeveloperMessageParamContentUnionMember1(TypedDict, total=False): + text: Required[str] + + type: Required[Literal["text"]] + + +class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): + content: Required[Union[str, Iterable[MessageChatCompletionDeveloperMessageParamContentUnionMember1]]] + + role: Required[Literal["developer"]] + + name: str + + +Message: TypeAlias = Union[ + MessageChatCompletionAssistantMessageParamInput, + MessageChatCompletionToolMessageParam, + MessageChatCompletionUserMessageParamInput, + MessageChatCompletionSystemMessageParam, + MessageChatCompletionFunctionMessageParam, + MessageChatCompletionDeveloperMessageParam, +] + + +class Options(TypedDict, total=False): + custom_eval_criteria: Iterable[object] + + disable_persistence: bool + + disable_trustworthiness: bool + + log: SequenceNotStr[str] + + max_tokens: int + + model: str + + num_candidate_responses: int + + num_consistency_samples: int + + num_self_reflections: int + + reasoning_effort: str + + similarity_measure: str + + use_self_reflection: bool + + +class ToolFunction(TypedDict, total=False): + name: Required[str] + + description: str + + parameters: object + + strict: Optional[bool] + + +class Tool(TypedDict, total=False): + function: Required[ToolFunction] + + type: Required[Literal["function"]] diff --git a/src/codex/types/project_detect_response.py b/src/codex/types/project_detect_response.py new file mode 100644 index 00000000..27044c18 --- /dev/null +++ b/src/codex/types/project_detect_response.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from .._models import BaseModel + +__all__ = ["ProjectDetectResponse", "DeterministicGuardrailsResults", "EvalScores"] + + +class DeterministicGuardrailsResults(BaseModel): + guardrail_name: str + + should_guardrail: bool + + fallback_message: Optional[str] = None + + matches: Optional[List[str]] = None + + +class EvalScores(BaseModel): + guardrailed_fallback_message: Optional[str] = None + + score: Optional[float] = None + + triggered: bool + + triggered_escalation: bool + + triggered_guardrail: bool + + failed: Optional[bool] = None + + log: Optional[object] = None + + +class ProjectDetectResponse(BaseModel): + deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None + """Results from deterministic guardrails applied to the response.""" + + escalated_to_sme: bool + """ + True if the question should be escalated to Codex for an SME to review, False + otherwise. When True, a lookup is performed, which logs this query in the + project for SMEs to answer, if it does not already exist. + """ + + eval_scores: Dict[str, EvalScores] + """ + Evaluation scores for the original response along with a boolean flag, `failed`, + indicating whether the score is below the threshold. + """ + + expert_answer: Optional[str] = None + """ + Alternate SME-provided answer from Codex if a relevant answer was found in the + Codex Project, or None otherwise. + """ + + expert_review_guardrail_explanation: Optional[str] = None + """Explanation from a similar bad query log that caused this to be guardrailed""" + + should_guardrail: bool + """ + True if the response should be guardrailed by the AI system, False if the + response is okay to return to the user. + """ diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 84ad6cdc..cfbfe417 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -11,6 +11,7 @@ from codex.types import ( ProjectListResponse, ProjectReturnSchema, + ProjectDetectResponse, ProjectRetrieveResponse, ProjectValidateResponse, ProjectInviteSmeResponse, @@ -436,6 +437,202 @@ def test_path_params_delete(self, client: Codex) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_detect(self, client: Codex) -> None: + project = client.projects.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + ) + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_detect_with_all_params(self, client: Codex) -> None: + project = client.projects.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + constrain_outputs=["string"], + eval_config={ + "custom_evals": { + "evals": { + "foo": { + "criteria": "criteria", + "eval_key": "eval_key", + "name": "name", + "context_identifier": "context_identifier", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "is_default": True, + "priority": 0, + "query_identifier": "query_identifier", + "response_identifier": "response_identifier", + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + } + } + }, + "default_evals": { + "context_sufficiency": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "query_ease": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_groundedness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_helpfulness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "trustworthiness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + }, + }, + messages=[ + { + "role": "assistant", + "audio": {"id": "id"}, + "content": "string", + "function_call": { + "arguments": "arguments", + "name": "name", + }, + "name": "name", + "refusal": "refusal", + "tool_calls": [ + { + "id": "id", + "function": { + "arguments": "arguments", + "name": "name", + }, + "type": "function", + } + ], + } + ], + options={ + "custom_eval_criteria": [{}], + "disable_persistence": True, + "disable_trustworthiness": True, + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "num_self_reflections": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + rewritten_question="rewritten_question", + task="task", + tools=[ + { + "function": { + "name": "name", + "description": "description", + "parameters": {}, + "strict": True, + }, + "type": "function", + } + ], + ) + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_detect(self, client: Codex) -> None: + response = client.projects.with_raw_response.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_detect(self, client: Codex) -> None: + with client.projects.with_streaming_response.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_detect(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.detect( + project_id="", + context="context", + query="x", + response="string", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_export(self, client: Codex) -> None: @@ -1127,6 +1324,202 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_detect(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + ) + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_detect_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + constrain_outputs=["string"], + eval_config={ + "custom_evals": { + "evals": { + "foo": { + "criteria": "criteria", + "eval_key": "eval_key", + "name": "name", + "context_identifier": "context_identifier", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "is_default": True, + "priority": 0, + "query_identifier": "query_identifier", + "response_identifier": "response_identifier", + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + } + } + }, + "default_evals": { + "context_sufficiency": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "query_ease": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_groundedness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "response_helpfulness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + "trustworthiness": { + "eval_key": "eval_key", + "name": "name", + "enabled": True, + "guardrailed_fallback_message": "guardrailed_fallback_message", + "priority": 0, + "should_escalate": True, + "should_guardrail": True, + "threshold": 0, + "threshold_direction": "above", + }, + }, + }, + messages=[ + { + "role": "assistant", + "audio": {"id": "id"}, + "content": "string", + "function_call": { + "arguments": "arguments", + "name": "name", + }, + "name": "name", + "refusal": "refusal", + "tool_calls": [ + { + "id": "id", + "function": { + "arguments": "arguments", + "name": "name", + }, + "type": "function", + } + ], + } + ], + options={ + "custom_eval_criteria": [{}], + "disable_persistence": True, + "disable_trustworthiness": True, + "log": ["string"], + "max_tokens": 0, + "model": "model", + "num_candidate_responses": 0, + "num_consistency_samples": 0, + "num_self_reflections": 0, + "reasoning_effort": "reasoning_effort", + "similarity_measure": "similarity_measure", + "use_self_reflection": True, + }, + quality_preset="best", + rewritten_question="rewritten_question", + task="task", + tools=[ + { + "function": { + "name": "name", + "description": "description", + "parameters": {}, + "strict": True, + }, + "type": "function", + } + ], + ) + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_detect(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_detect(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.detect( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + context="context", + query="x", + response="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectDetectResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_detect(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.detect( + project_id="", + context="context", + query="x", + response="string", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_export(self, async_client: AsyncCodex) -> None: From 43c6ce4126cba06817762052ab955c98e35de6a0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:25:31 +0000 Subject: [PATCH 265/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f4710698..c412e974 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.28" + ".": "0.1.0-alpha.29" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4a249aac..5add6cca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.28" +version = "0.1.0-alpha.29" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 82d84e82..77a43dfd 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.28" # x-release-please-version +__version__ = "0.1.0-alpha.29" # x-release-please-version From 27c54e764d2980ae588b01d16c5128905ab884e1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 21:18:24 +0000 Subject: [PATCH 266/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 4739e919..165ff244 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: b54b36ebcaf88c1ddb6d51d24da75420 +openapi_spec_hash: fa4be08d4125b8da612cbe21dfffdf51 config_hash: 48c3812186c899cdef23cc8de76bd2aa From 71b2b443379c3dc42dff28dcbfc5312c78b1e675 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:18:22 +0000 Subject: [PATCH 267/320] feat(api): api update --- .stats.yml | 2 +- .../types/projects/query_log_list_by_group_response.py | 6 ++++++ src/codex/types/projects/query_log_list_groups_response.py | 6 ++++++ src/codex/types/projects/query_log_list_response.py | 6 ++++++ src/codex/types/projects/query_log_retrieve_response.py | 6 ++++++ .../projects/remediation_list_resolved_logs_response.py | 6 ++++++ 6 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 165ff244..6e67edaa 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: fa4be08d4125b8da612cbe21dfffdf51 +openapi_spec_hash: 8a01495b5f02dff23dab6f0f5f74a1f7 config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index fc2e9043..fcc0589c 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -430,6 +430,12 @@ class QueryLogsByGroupQueryLog(BaseModel): itself. """ + original_assistant_response: Optional[str] = None + """The original assistant response that would have been displayed to the user. + + This may be `None` if this is a tool call step. + """ + original_question: Optional[str] = None """The original question that was asked before any rewriting or processing. diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index e61b6512..f3715b73 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -425,6 +425,12 @@ class QueryLogListGroupsResponse(BaseModel): itself. """ + original_assistant_response: Optional[str] = None + """The original assistant response that would have been displayed to the user. + + This may be `None` if this is a tool call step. + """ + original_question: Optional[str] = None """The original question that was asked before any rewriting or processing. diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 6494d83f..8d90a44d 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -410,6 +410,12 @@ class QueryLogListResponse(BaseModel): itself. """ + original_assistant_response: Optional[str] = None + """The original assistant response that would have been displayed to the user. + + This may be `None` if this is a tool call step. + """ + original_question: Optional[str] = None """The original question that was asked before any rewriting or processing. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index abfaeebb..afd1a915 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -417,6 +417,12 @@ class QueryLogRetrieveResponse(BaseModel): itself. """ + original_assistant_response: Optional[str] = None + """The original assistant response that would have been displayed to the user. + + This may be `None` if this is a tool call step. + """ + original_question: Optional[str] = None """The original question that was asked before any rewriting or processing. diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index e2239c41..d93bc338 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -417,6 +417,12 @@ class QueryLog(BaseModel): itself. """ + original_assistant_response: Optional[str] = None + """The original assistant response that would have been displayed to the user. + + This may be `None` if this is a tool call step. + """ + original_question: Optional[str] = None """The original question that was asked before any rewriting or processing. From 5161617a1e2a665f9cbcc429f48f475746095d5d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 21:18:25 +0000 Subject: [PATCH 268/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 6e67edaa..acf1145e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 8a01495b5f02dff23dab6f0f5f74a1f7 +openapi_spec_hash: 3c5027a5bd3fb174b9b7f26bd5cb0eba config_hash: 48c3812186c899cdef23cc8de76bd2aa From 57324362e3fd78b5f60f24879279c0c484fb9bf1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 21:18:06 +0000 Subject: [PATCH 269/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 92 +++++++++++++++++-- .../project_retrieve_analytics_params.py | 24 ++++- .../project_retrieve_analytics_response.py | 22 ++++- tests/api_resources/test_projects.py | 2 + 5 files changed, 126 insertions(+), 16 deletions(-) diff --git a/.stats.yml b/.stats.yml index acf1145e..bf5b2a22 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 3c5027a5bd3fb174b9b7f26bd5cb0eba +openapi_spec_hash: 37c29bad42e8605b1c087d22c8d48f30 config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 4575e8a2..e94a6d34 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -564,8 +564,9 @@ def retrieve_analytics( self, project_id: str, *, - end: int | Omit = omit, - start: int | Omit = omit, + end: Optional[int] | Omit = omit, + metadata_filters: Optional[str] | Omit = omit, + start: Optional[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, @@ -574,12 +575,46 @@ def retrieve_analytics( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectRetrieveAnalyticsResponse: """ - Get Project Analytics Route + Retrieve analytics data for a project including queries, bad responses, and + answers published. + + **Metadata Filtering:** + - Filter by custom metadata fields using key-value pairs + - Supports single values: `{"department": "Engineering"}` + - Supports multiple values: `{"priority": ["high", "medium"]}` + - Supports null/missing values: `{"department": []}` or `{"department": [null]}` + + **Available Metadata Fields:** + - Only metadata keys that exist on query logs are returned in `metadata_fields` + - Fields with ≤12 unique values show as "select" type with checkbox options + - Fields with >12 unique values show as "input" type for text search + - Fields with no data are excluded from the response entirely + + **Null Value Behavior:** + - Empty arrays `[]` are automatically converted to `[null]` to filter for records where the metadata field is missing or null + - Use `[null]` explicitly to filter for records where the field is missing or null + - Use `["value1", null, "value2"]` to include both specific values and null values + - Records match if the metadata field is null, missing from custom_metadata, or custom_metadata itself is null + + **Date Filtering:** + - Provide `start` only: filter logs created at or after this timestamp + - Provide `end` only: filter logs created at or before this timestamp + - Provide both: filter logs created within the time range + - Provide neither: include all logs regardless of creation time Args: - end: End timestamp in seconds since epoch + end: Filter logs created at or before this timestamp (epoch seconds). Can be used + alone for upper-bound filtering. - start: Start timestamp in seconds since epoch + metadata_filters: + Metadata filters as JSON string. Examples: + - Single value: '{"department": "Engineering"}' + - Multiple values: '{"priority": ["high", "medium"]}' + - Null/missing values: '{"department": []}' or '{"department": [null]}' + - Mixed values: '{"status": ["active", null, "pending"]}' + + start: Filter logs created at or after this timestamp (epoch seconds). Can be used + alone for lower-bound filtering. extra_headers: Send extra headers @@ -601,6 +636,7 @@ def retrieve_analytics( query=maybe_transform( { "end": end, + "metadata_filters": metadata_filters, "start": start, }, project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, @@ -1301,8 +1337,9 @@ async def retrieve_analytics( self, project_id: str, *, - end: int | Omit = omit, - start: int | Omit = omit, + end: Optional[int] | Omit = omit, + metadata_filters: Optional[str] | Omit = omit, + start: Optional[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, @@ -1311,12 +1348,46 @@ async def retrieve_analytics( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectRetrieveAnalyticsResponse: """ - Get Project Analytics Route + Retrieve analytics data for a project including queries, bad responses, and + answers published. + + **Metadata Filtering:** + - Filter by custom metadata fields using key-value pairs + - Supports single values: `{"department": "Engineering"}` + - Supports multiple values: `{"priority": ["high", "medium"]}` + - Supports null/missing values: `{"department": []}` or `{"department": [null]}` + + **Available Metadata Fields:** + - Only metadata keys that exist on query logs are returned in `metadata_fields` + - Fields with ≤12 unique values show as "select" type with checkbox options + - Fields with >12 unique values show as "input" type for text search + - Fields with no data are excluded from the response entirely + + **Null Value Behavior:** + - Empty arrays `[]` are automatically converted to `[null]` to filter for records where the metadata field is missing or null + - Use `[null]` explicitly to filter for records where the field is missing or null + - Use `["value1", null, "value2"]` to include both specific values and null values + - Records match if the metadata field is null, missing from custom_metadata, or custom_metadata itself is null + + **Date Filtering:** + - Provide `start` only: filter logs created at or after this timestamp + - Provide `end` only: filter logs created at or before this timestamp + - Provide both: filter logs created within the time range + - Provide neither: include all logs regardless of creation time Args: - end: End timestamp in seconds since epoch + end: Filter logs created at or before this timestamp (epoch seconds). Can be used + alone for upper-bound filtering. + + metadata_filters: + Metadata filters as JSON string. Examples: + - Single value: '{"department": "Engineering"}' + - Multiple values: '{"priority": ["high", "medium"]}' + - Null/missing values: '{"department": []}' or '{"department": [null]}' + - Mixed values: '{"status": ["active", null, "pending"]}' - start: Start timestamp in seconds since epoch + start: Filter logs created at or after this timestamp (epoch seconds). Can be used + alone for lower-bound filtering. extra_headers: Send extra headers @@ -1338,6 +1409,7 @@ async def retrieve_analytics( query=await async_maybe_transform( { "end": end, + "metadata_filters": metadata_filters, "start": start, }, project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, diff --git a/src/codex/types/project_retrieve_analytics_params.py b/src/codex/types/project_retrieve_analytics_params.py index c5f9a48c..47719ca9 100644 --- a/src/codex/types/project_retrieve_analytics_params.py +++ b/src/codex/types/project_retrieve_analytics_params.py @@ -2,14 +2,30 @@ from __future__ import annotations +from typing import Optional from typing_extensions import TypedDict __all__ = ["ProjectRetrieveAnalyticsParams"] class ProjectRetrieveAnalyticsParams(TypedDict, total=False): - end: int - """End timestamp in seconds since epoch""" + end: Optional[int] + """Filter logs created at or before this timestamp (epoch seconds). - start: int - """Start timestamp in seconds since epoch""" + Can be used alone for upper-bound filtering. + """ + + metadata_filters: Optional[str] + """Metadata filters as JSON string. + + Examples: - Single value: '{"department": "Engineering"}' - Multiple values: + '{"priority": ["high", "medium"]}' - Null/missing values: '{"department": []}' + or '{"department": [null]}' - Mixed values: '{"status": ["active", null, + "pending"]}' + """ + + start: Optional[int] + """Filter logs created at or after this timestamp (epoch seconds). + + Can be used alone for lower-bound filtering. + """ diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py index b1e5d85e..77110c2a 100644 --- a/src/codex/types/project_retrieve_analytics_response.py +++ b/src/codex/types/project_retrieve_analytics_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List +from typing import Dict, List, Optional +from typing_extensions import Literal from .._models import BaseModel @@ -11,6 +12,7 @@ "BadResponses", "BadResponsesResponsesByType", "Queries", + "MetadataField", ] @@ -44,9 +46,27 @@ class Queries(BaseModel): total: int +class MetadataField(BaseModel): + field_type: Literal["select", "input"] + """Field type: 'select' for checkbox selection, 'input' for text input""" + + key: str + """Metadata field key""" + + values: Optional[List[Optional[str]]] = None + """Possible values for this metadata field (None if more than 12 values). + + Array elements may include null to represent logs where the metadata key is + missing or null. + """ + + class ProjectRetrieveAnalyticsResponse(BaseModel): answers_published: AnswersPublished bad_responses: BadResponses queries: Queries + + metadata_fields: Optional[List[MetadataField]] = None + """Available metadata fields for filtering""" diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index cfbfe417..564da9a3 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -743,6 +743,7 @@ def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None: project = client.projects.retrieve_analytics( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=0, + metadata_filters="metadata_filters", start=0, ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) @@ -1630,6 +1631,7 @@ async def test_method_retrieve_analytics_with_all_params(self, async_client: Asy project = await async_client.projects.retrieve_analytics( project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=0, + metadata_filters="metadata_filters", start=0, ) assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) From 03de67ee1307370f6aa4efe327ecf58d69d669f7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 02:11:26 +0000 Subject: [PATCH 270/320] chore(internal): detect missing future annotations with ruff --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5add6cca..da70560c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -224,6 +224,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -246,6 +248,8 @@ unfixable = [ "T203", ] +extend-safe-fixes = ["FA102"] + [tool.ruff.lint.flake8-tidy-imports.banned-api] "functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" From 5e5c391cfc88aec393290d570ed18cfcfd35aced Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:18:02 +0000 Subject: [PATCH 271/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_detect_response.py | 2 -- src/codex/types/project_validate_response.py | 9 --------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.stats.yml b/.stats.yml index bf5b2a22..f3581492 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 37c29bad42e8605b1c087d22c8d48f30 +openapi_spec_hash: 57e7632444ad87c5331df5d8fd648b7d config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/project_detect_response.py b/src/codex/types/project_detect_response.py index 27044c18..cb173c37 100644 --- a/src/codex/types/project_detect_response.py +++ b/src/codex/types/project_detect_response.py @@ -28,8 +28,6 @@ class EvalScores(BaseModel): triggered_guardrail: bool - failed: Optional[bool] = None - log: Optional[object] = None diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index 1c0cc4a2..d635a16d 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -28,8 +28,6 @@ class EvalScores(BaseModel): triggered_guardrail: bool - failed: Optional[bool] = None - log: Optional[object] = None @@ -59,13 +57,6 @@ class ProjectValidateResponse(BaseModel): expert_review_guardrail_explanation: Optional[str] = None """Explanation from a similar bad query log that caused this to be guardrailed""" - is_bad_response: bool - """True if the response is flagged as potentially bad, False otherwise. - - When True, a lookup is performed, which logs this query in the project for SMEs - to answer, if it does not already exist. - """ - log_id: str """The UUID of the query log entry created for this validation request.""" From 63a3796930f66fc579e11b5d28647035ed0fb8f4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 18:18:07 +0000 Subject: [PATCH 272/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_detect_response.py | 7 +++-- src/codex/types/project_list_response.py | 6 +++++ src/codex/types/project_retrieve_response.py | 6 +++++ src/codex/types/project_return_schema.py | 6 +++++ src/codex/types/project_validate_response.py | 7 +++-- .../types/projects/eval_list_response.py | 6 +++++ .../query_log_list_by_group_response.py | 26 ++++++++++++++++--- .../query_log_list_groups_response.py | 26 ++++++++++++++++--- .../types/projects/query_log_list_response.py | 26 ++++++++++++++++--- .../projects/query_log_retrieve_response.py | 26 ++++++++++++++++--- ...remediation_list_resolved_logs_response.py | 26 ++++++++++++++++--- 12 files changed, 150 insertions(+), 20 deletions(-) diff --git a/.stats.yml b/.stats.yml index f3581492..2e711393 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 57e7632444ad87c5331df5d8fd648b7d +openapi_spec_hash: c894ce3fb9db92c69816f06896e30067 config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/project_detect_response.py b/src/codex/types/project_detect_response.py index cb173c37..cdee7dcb 100644 --- a/src/codex/types/project_detect_response.py +++ b/src/codex/types/project_detect_response.py @@ -54,8 +54,11 @@ class ProjectDetectResponse(BaseModel): Codex Project, or None otherwise. """ - expert_review_guardrail_explanation: Optional[str] = None - """Explanation from a similar bad query log that caused this to be guardrailed""" + expert_guardrail_override_explanation: Optional[str] = None + """ + Explanation of why the response was either guardrailed or not guardrailed by + expert review. Expert review will override the original guardrail decision. + """ should_guardrail: bool """ diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 9fd609f5..5b2d6926 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -30,6 +30,12 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): how """ + display_name: str + """Human-friendly name for display. + + For default evals, prefer standardized labels; otherwise use configured name. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 949ce254..399ddd98 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -28,6 +28,12 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): how """ + display_name: str + """Human-friendly name for display. + + For default evals, prefer standardized labels; otherwise use configured name. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index cf2f3a89..923de6bc 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -28,6 +28,12 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): how """ + display_name: str + """Human-friendly name for display. + + For default evals, prefer standardized labels; otherwise use configured name. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index d635a16d..458e4fc5 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -54,8 +54,11 @@ class ProjectValidateResponse(BaseModel): Codex Project, or None otherwise. """ - expert_review_guardrail_explanation: Optional[str] = None - """Explanation from a similar bad query log that caused this to be guardrailed""" + expert_guardrail_override_explanation: Optional[str] = None + """ + Explanation of why the response was either guardrailed or not guardrailed by + expert review. Expert review will override the original guardrail decision. + """ log_id: str """The UUID of the query log entry created for this validation request.""" diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py index 073312d8..572de97b 100644 --- a/src/codex/types/projects/eval_list_response.py +++ b/src/codex/types/projects/eval_list_response.py @@ -15,6 +15,12 @@ class Eval(BaseModel): how """ + display_name: str + """Human-friendly name for display. + + For default evals, prefer standardized labels; otherwise use configured name. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index fcc0589c..632a3757 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -50,24 +50,32 @@ class QueryLogsByGroupQueryLogFormattedEscalationEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class QueryLogsByGroupQueryLogFormattedEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class QueryLogsByGroupQueryLogFormattedGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] @@ -384,6 +392,9 @@ class QueryLogsByGroupQueryLog(BaseModel): escalation_evals: Optional[List[str]] = None """Evals that should trigger escalation to SME""" + eval_display_names: Optional[Dict[str, str]] = None + """Mapping of eval keys to display names at time of creation""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -402,6 +413,18 @@ class QueryLogsByGroupQueryLog(BaseModel): Used to log tool calls in the query log. """ + expert_guardrail_override_explanation: Optional[str] = None + """ + Explanation of why the response was either guardrailed or not guardrailed by + expert review. Expert review will override the original guardrail decision. + """ + + expert_override_log_id: Optional[str] = None + """ + ID of the query log with expert review that overrode the original guardrail + decision. + """ + expert_review_created_at: Optional[datetime] = None """When the expert review was created""" @@ -449,9 +472,6 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - similar_query_log_guardrail_explanation: Optional[str] = None - """Explanation from a similar bad query log that caused this to be guardrailed""" - tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index f3715b73..3d894f16 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -47,24 +47,32 @@ class FormattedEscalationEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedNonGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] @@ -379,6 +387,9 @@ class QueryLogListGroupsResponse(BaseModel): escalation_evals: Optional[List[str]] = None """Evals that should trigger escalation to SME""" + eval_display_names: Optional[Dict[str, str]] = None + """Mapping of eval keys to display names at time of creation""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -397,6 +408,18 @@ class QueryLogListGroupsResponse(BaseModel): Used to log tool calls in the query log. """ + expert_guardrail_override_explanation: Optional[str] = None + """ + Explanation of why the response was either guardrailed or not guardrailed by + expert review. Expert review will override the original guardrail decision. + """ + + expert_override_log_id: Optional[str] = None + """ + ID of the query log with expert review that overrode the original guardrail + decision. + """ + expert_review_created_at: Optional[datetime] = None """When the expert review was created""" @@ -444,9 +467,6 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - similar_query_log_guardrail_explanation: Optional[str] = None - """Explanation from a similar bad query log that caused this to be guardrailed""" - tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 8d90a44d..8e57871a 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -47,24 +47,32 @@ class FormattedEscalationEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedNonGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] @@ -367,6 +375,9 @@ class QueryLogListResponse(BaseModel): escalation_evals: Optional[List[str]] = None """Evals that should trigger escalation to SME""" + eval_display_names: Optional[Dict[str, str]] = None + """Mapping of eval keys to display names at time of creation""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -385,6 +396,18 @@ class QueryLogListResponse(BaseModel): Used to log tool calls in the query log. """ + expert_guardrail_override_explanation: Optional[str] = None + """ + Explanation of why the response was either guardrailed or not guardrailed by + expert review. Expert review will override the original guardrail decision. + """ + + expert_override_log_id: Optional[str] = None + """ + ID of the query log with expert review that overrode the original guardrail + decision. + """ + expert_review_created_at: Optional[datetime] = None """When the expert review was created""" @@ -429,9 +452,6 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - similar_query_log_guardrail_explanation: Optional[str] = None - """Explanation from a similar bad query log that caused this to be guardrailed""" - tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index afd1a915..ef1aee65 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -47,24 +47,32 @@ class FormattedEscalationEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class FormattedNonGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] @@ -371,6 +379,9 @@ class QueryLogRetrieveResponse(BaseModel): escalation_evals: Optional[List[str]] = None """Evals that should trigger escalation to SME""" + eval_display_names: Optional[Dict[str, str]] = None + """Mapping of eval keys to display names at time of creation""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -389,6 +400,18 @@ class QueryLogRetrieveResponse(BaseModel): Used to log tool calls in the query log. """ + expert_guardrail_override_explanation: Optional[str] = None + """ + Explanation of why the response was either guardrailed or not guardrailed by + expert review. Expert review will override the original guardrail decision. + """ + + expert_override_log_id: Optional[str] = None + """ + ID of the query log with expert review that overrode the original guardrail + decision. + """ + expert_review_created_at: Optional[datetime] = None """When the expert review was created""" @@ -436,9 +459,6 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - similar_query_log_guardrail_explanation: Optional[str] = None - """Explanation from a similar bad query log that caused this to be guardrailed""" - tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index d93bc338..f85be784 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -48,24 +48,32 @@ class QueryLogFormattedEscalationEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class QueryLogFormattedEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class QueryLogFormattedGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] class QueryLogFormattedNonGuardrailEvalScores(BaseModel): + display_name: str + score: float status: Literal["pass", "fail"] @@ -374,6 +382,9 @@ class QueryLog(BaseModel): escalation_evals: Optional[List[str]] = None """Evals that should trigger escalation to SME""" + eval_display_names: Optional[Dict[str, str]] = None + """Mapping of eval keys to display names at time of creation""" + eval_issue_labels: Optional[List[str]] = None """Labels derived from evaluation scores""" @@ -392,6 +403,18 @@ class QueryLog(BaseModel): Used to log tool calls in the query log. """ + expert_guardrail_override_explanation: Optional[str] = None + """ + Explanation of why the response was either guardrailed or not guardrailed by + expert review. Expert review will override the original guardrail decision. + """ + + expert_override_log_id: Optional[str] = None + """ + ID of the query log with expert review that overrode the original guardrail + decision. + """ + expert_review_created_at: Optional[datetime] = None """When the expert review was created""" @@ -436,9 +459,6 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - similar_query_log_guardrail_explanation: Optional[str] = None - """Explanation from a similar bad query log that caused this to be guardrailed""" - tools: Optional[List[QueryLogTool]] = None """Tools to use for the LLM call. From fc78002a9a646121234e140743a7a3839ec8e46e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 21:46:19 +0000 Subject: [PATCH 273/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c412e974..52b3e834 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.29" + ".": "0.1.0-alpha.30" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index da70560c..fcc32344 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.29" +version = "0.1.0-alpha.30" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 77a43dfd..a008ac3b 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.29" # x-release-please-version +__version__ = "0.1.0-alpha.30" # x-release-please-version From 7df8bb9ab0548b28b070ec1c470e70b82ca938ee Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 00:18:03 +0000 Subject: [PATCH 274/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_detect_response.py | 17 ++--------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2e711393..f15ec6dc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: c894ce3fb9db92c69816f06896e30067 +openapi_spec_hash: 2a1aded6c0f311e6133ba9beadc08062 config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/project_detect_response.py b/src/codex/types/project_detect_response.py index cdee7dcb..4e4b74f7 100644 --- a/src/codex/types/project_detect_response.py +++ b/src/codex/types/project_detect_response.py @@ -1,20 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import Dict, Optional from .._models import BaseModel -__all__ = ["ProjectDetectResponse", "DeterministicGuardrailsResults", "EvalScores"] - - -class DeterministicGuardrailsResults(BaseModel): - guardrail_name: str - - should_guardrail: bool - - fallback_message: Optional[str] = None - - matches: Optional[List[str]] = None +__all__ = ["ProjectDetectResponse", "EvalScores"] class EvalScores(BaseModel): @@ -32,9 +22,6 @@ class EvalScores(BaseModel): class ProjectDetectResponse(BaseModel): - deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None - """Results from deterministic guardrails applied to the response.""" - escalated_to_sme: bool """ True if the question should be escalated to Codex for an SME to review, False From 7d997a91c92220f16cb67bc308728bcaa8b6c115 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:18:05 +0000 Subject: [PATCH 275/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_list_response.py | 30 ++++++++++++++++++++ src/codex/types/project_retrieve_response.py | 30 ++++++++++++++++++++ src/codex/types/project_return_schema.py | 30 ++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f15ec6dc..0b4fc396 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 2a1aded6c0f311e6133ba9beadc08062 +openapi_spec_hash: 26ac4152b9f12f890d1df1cf1a219f78 config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 5b2d6926..567eff87 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -101,6 +101,12 @@ class ProjectConfigEvalConfigCustomEvals(BaseModel): class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -142,6 +148,12 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -183,6 +195,12 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -224,6 +242,12 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -265,6 +289,12 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 399ddd98..40b4f18c 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -99,6 +99,12 @@ class ConfigEvalConfigCustomEvals(BaseModel): class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -140,6 +146,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -181,6 +193,12 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -222,6 +240,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -263,6 +287,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 923de6bc..4bab16f7 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -99,6 +99,12 @@ class ConfigEvalConfigCustomEvals(BaseModel): class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -140,6 +146,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -181,6 +193,12 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -222,6 +240,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -263,6 +287,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + display_name: str + """Human-friendly name for display. + + For default evals, use standardized labels from DEFAULT_EVAL_ISSUE_TYPE_LABELS. + """ + eval_key: str """ Unique key for eval metric - currently maps to the TrustworthyRAG name property From 30d4594d758a103e87341c5fe356f7ac2617fab1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:06:43 +0000 Subject: [PATCH 276/320] chore: bump `httpx-aiohttp` version to 0.1.9 --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- requirements.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fcc32344..206383e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ Homepage = "https://github.com/cleanlab/codex-python" Repository = "https://github.com/cleanlab/codex-python" [project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index 2aaac36f..d728372e 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -56,7 +56,7 @@ httpx==0.28.1 # via codex-sdk # via httpx-aiohttp # via respx -httpx-aiohttp==0.1.8 +httpx-aiohttp==0.1.9 # via codex-sdk idna==3.4 # via anyio diff --git a/requirements.lock b/requirements.lock index a0182743..4b916da1 100644 --- a/requirements.lock +++ b/requirements.lock @@ -43,7 +43,7 @@ httpcore==1.0.9 httpx==0.28.1 # via codex-sdk # via httpx-aiohttp -httpx-aiohttp==0.1.8 +httpx-aiohttp==0.1.9 # via codex-sdk idna==3.4 # via anyio From ddf80314c8990f91c8c228df7714e1b167574ba9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:18:07 +0000 Subject: [PATCH 277/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/projects/query_log_list_by_group_response.py | 3 +++ src/codex/types/projects/query_log_list_groups_response.py | 3 +++ src/codex/types/projects/query_log_list_response.py | 3 +++ src/codex/types/projects/query_log_retrieve_response.py | 3 +++ .../types/projects/remediation_list_resolved_logs_response.py | 3 +++ 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 0b4fc396..4b93f8a5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 26ac4152b9f12f890d1df1cf1a219f78 +openapi_spec_hash: d555131ff52c78852f82acda3348224b config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 632a3757..3b4544e6 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -472,6 +472,9 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + served_remediation_id: Optional[str] = None + """ID of the remediation that was served if cache hit, otherwise None.""" + tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 3d894f16..01b2824c 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -467,6 +467,9 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + served_remediation_id: Optional[str] = None + """ID of the remediation that was served if cache hit, otherwise None.""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 8e57871a..d401dd67 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -452,6 +452,9 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + served_remediation_id: Optional[str] = None + """ID of the remediation that was served if cache hit, otherwise None.""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index ef1aee65..5ca091f1 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -459,6 +459,9 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + served_remediation_id: Optional[str] = None + """ID of the remediation that was served if cache hit, otherwise None.""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index f85be784..3cb91316 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -459,6 +459,9 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + served_remediation_id: Optional[str] = None + """ID of the remediation that was served if cache hit, otherwise None.""" + tools: Optional[List[QueryLogTool]] = None """Tools to use for the LLM call. From 3f99afa2e7035be7ccfa35dda65c21e13d7a6839 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 01:17:42 +0000 Subject: [PATCH 278/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_create_params.py | 2 ++ src/codex/types/project_list_response.py | 2 ++ src/codex/types/project_retrieve_response.py | 2 ++ src/codex/types/project_return_schema.py | 2 ++ src/codex/types/project_update_params.py | 2 ++ src/codex/types/projects/query_log_list_by_group_response.py | 3 +++ src/codex/types/projects/query_log_list_groups_response.py | 3 +++ src/codex/types/projects/query_log_list_response.py | 3 +++ src/codex/types/projects/query_log_retrieve_response.py | 3 +++ .../types/projects/remediation_list_resolved_logs_response.py | 3 +++ tests/api_resources/test_projects.py | 4 ++++ 12 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 4b93f8a5..728c28d8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: d555131ff52c78852f82acda3348224b +openapi_spec_hash: 218b257d101099fcf62b8afde2fc5d8c config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index c17be679..bd14d3dd 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -354,6 +354,8 @@ class ConfigEvalConfig(TypedDict, total=False): class Config(TypedDict, total=False): + ai_guidance_threshold: float + clustering_use_llm_matching: bool eval_config: ConfigEvalConfig diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 567eff87..209b1a39 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -381,6 +381,8 @@ class ProjectConfigEvalConfig(BaseModel): class ProjectConfig(BaseModel): + ai_guidance_threshold: Optional[float] = None + clustering_use_llm_matching: Optional[bool] = None eval_config: Optional[ProjectConfigEvalConfig] = None diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 40b4f18c..b26c9c1b 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -379,6 +379,8 @@ class ConfigEvalConfig(BaseModel): class Config(BaseModel): + ai_guidance_threshold: Optional[float] = None + clustering_use_llm_matching: Optional[bool] = None eval_config: Optional[ConfigEvalConfig] = None diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 4bab16f7..10a8ea33 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -379,6 +379,8 @@ class ConfigEvalConfig(BaseModel): class Config(BaseModel): + ai_guidance_threshold: Optional[float] = None + clustering_use_llm_matching: Optional[bool] = None eval_config: Optional[ConfigEvalConfig] = None diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 4b4de76c..4ca5abfc 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -352,6 +352,8 @@ class ConfigEvalConfig(TypedDict, total=False): class Config(TypedDict, total=False): + ai_guidance_threshold: float + clustering_use_llm_matching: bool eval_config: ConfigEvalConfig diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 3b4544e6..5df928b6 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -374,6 +374,9 @@ class QueryLogsByGroupQueryLog(BaseModel): was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" + ai_guidance_id: Optional[str] = None + """ID of the AI guidance remediation that was created from this query log.""" + context: Optional[List[QueryLogsByGroupQueryLogContext]] = None """RAG context used for the query""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 01b2824c..727cac2a 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -369,6 +369,9 @@ class QueryLogListGroupsResponse(BaseModel): was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" + ai_guidance_id: Optional[str] = None + """ID of the AI guidance remediation that was created from this query log.""" + context: Optional[List[Context]] = None """RAG context used for the query""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index d401dd67..0a8b4275 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -357,6 +357,9 @@ class QueryLogListResponse(BaseModel): was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" + ai_guidance_id: Optional[str] = None + """ID of the AI guidance remediation that was created from this query log.""" + context: Optional[List[Context]] = None """RAG context used for the query""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 5ca091f1..13510f87 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -361,6 +361,9 @@ class QueryLogRetrieveResponse(BaseModel): was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" + ai_guidance_id: Optional[str] = None + """ID of the AI guidance remediation that was created from this query log.""" + context: Optional[List[Context]] = None """RAG context used for the query""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 3cb91316..16017b45 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -364,6 +364,9 @@ class QueryLog(BaseModel): was_cache_hit: Optional[bool] = None """If similar query already answered, or None if cache was not checked""" + ai_guidance_id: Optional[str] = None + """ID of the AI guidance remediation that was created from this query log.""" + context: Optional[List[QueryLogContext]] = None """RAG context used for the query""" diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 564da9a3..6b742b47 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -40,6 +40,7 @@ def test_method_create(self, client: Codex) -> None: def test_method_create_with_all_params(self, client: Codex) -> None: project = client.projects.create( config={ + "ai_guidance_threshold": 0, "clustering_use_llm_matching": True, "eval_config": { "custom_evals": { @@ -224,6 +225,7 @@ def test_method_update_with_all_params(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", auto_clustering_enabled=True, config={ + "ai_guidance_threshold": 0, "clustering_use_llm_matching": True, "eval_config": { "custom_evals": { @@ -928,6 +930,7 @@ async def test_method_create(self, async_client: AsyncCodex) -> None: async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.create( config={ + "ai_guidance_threshold": 0, "clustering_use_llm_matching": True, "eval_config": { "custom_evals": { @@ -1112,6 +1115,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", auto_clustering_enabled=True, config={ + "ai_guidance_threshold": 0, "clustering_use_llm_matching": True, "eval_config": { "custom_evals": { From d8902a45cc13a384c48ab08a2242af042856bbbf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 04:17:41 +0000 Subject: [PATCH 279/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 728c28d8..357f8086 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: 218b257d101099fcf62b8afde2fc5d8c +openapi_spec_hash: d719fa1f3cf8b6b5685d1c583e6d1a2a config_hash: 48c3812186c899cdef23cc8de76bd2aa From 6d8055861803702da8ca14aa9b07c2d322714ef7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:17:37 +0000 Subject: [PATCH 280/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/project_list_response.py | 2 ++ src/codex/types/project_retrieve_response.py | 2 ++ src/codex/types/project_return_schema.py | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 357f8086..3f2e10b3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 55 -openapi_spec_hash: d719fa1f3cf8b6b5685d1c583e6d1a2a +openapi_spec_hash: ef178c3ce0c31f0785212f1138ee8eee config_hash: 48c3812186c899cdef23cc8de76bd2aa diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 209b1a39..66d2037f 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -416,6 +416,8 @@ class Project(BaseModel): created_by_user_id: str + is_template: bool + name: str organization_id: str diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index b26c9c1b..694f590b 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -414,6 +414,8 @@ class ProjectRetrieveResponse(BaseModel): created_by_user_id: str + is_template: bool + name: str organization_id: str diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 10a8ea33..b8b54044 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -414,6 +414,8 @@ class ProjectReturnSchema(BaseModel): created_by_user_id: str + is_template: bool + name: str organization_id: str From bcb19b06320a5f122018efccce159e5f3354dcfa Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:21:00 +0000 Subject: [PATCH 281/320] feat(api): add create from template --- .stats.yml | 4 +- api.md | 1 + src/codex/resources/projects/projects.py | 111 ++++++++++++++++++ src/codex/types/__init__.py | 1 + .../project_create_from_template_params.py | 18 +++ tests/api_resources/test_projects.py | 90 ++++++++++++++ 6 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 src/codex/types/project_create_from_template_params.py diff --git a/.stats.yml b/.stats.yml index 3f2e10b3..5add63e3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 55 +configured_endpoints: 56 openapi_spec_hash: ef178c3ce0c31f0785212f1138ee8eee -config_hash: 48c3812186c899cdef23cc8de76bd2aa +config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 diff --git a/api.md b/api.md index 5d40e153..a2888743 100644 --- a/api.md +++ b/api.md @@ -153,6 +153,7 @@ Methods: - client.projects.update(project_id, \*\*params) -> ProjectReturnSchema - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None +- client.projects.create_from_template(\*\*params) -> ProjectReturnSchema - client.projects.detect(project_id, \*\*params) -> ProjectDetectResponse - client.projects.export(project_id) -> object - client.projects.invite_sme(project_id, \*\*params) -> ProjectInviteSmeResponse diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index e94a6d34..8aa1da79 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -23,6 +23,7 @@ project_validate_params, project_invite_sme_params, project_retrieve_analytics_params, + project_create_from_template_params, ) from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given from ..._utils import maybe_transform, strip_not_given, async_maybe_transform @@ -316,6 +317,55 @@ def delete( cast_to=NoneType, ) + def create_from_template( + self, + *, + organization_id: str, + template_project_id: str | Omit = omit, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectReturnSchema: + """ + Create a new project from a template project. + + 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._post( + "/api/projects/create-from-template", + body=maybe_transform( + { + "organization_id": organization_id, + "description": description, + "name": name, + }, + project_create_from_template_params.ProjectCreateFromTemplateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"template_project_id": template_project_id}, + project_create_from_template_params.ProjectCreateFromTemplateParams, + ), + ), + cast_to=ProjectReturnSchema, + ) + def detect( self, project_id: str, @@ -1089,6 +1139,55 @@ async def delete( cast_to=NoneType, ) + async def create_from_template( + self, + *, + organization_id: str, + template_project_id: str | Omit = omit, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ProjectReturnSchema: + """ + Create a new project from a template project. + + 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._post( + "/api/projects/create-from-template", + body=await async_maybe_transform( + { + "organization_id": organization_id, + "description": description, + "name": name, + }, + project_create_from_template_params.ProjectCreateFromTemplateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"template_project_id": template_project_id}, + project_create_from_template_params.ProjectCreateFromTemplateParams, + ), + ), + cast_to=ProjectReturnSchema, + ) + async def detect( self, project_id: str, @@ -1635,6 +1734,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.delete = to_raw_response_wrapper( projects.delete, ) + self.create_from_template = to_raw_response_wrapper( + projects.create_from_template, + ) self.detect = to_raw_response_wrapper( projects.detect, ) @@ -1687,6 +1789,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.delete = async_to_raw_response_wrapper( projects.delete, ) + self.create_from_template = async_to_raw_response_wrapper( + projects.create_from_template, + ) self.detect = async_to_raw_response_wrapper( projects.detect, ) @@ -1739,6 +1844,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.delete = to_streamed_response_wrapper( projects.delete, ) + self.create_from_template = to_streamed_response_wrapper( + projects.create_from_template, + ) self.detect = to_streamed_response_wrapper( projects.detect, ) @@ -1791,6 +1899,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.delete = async_to_streamed_response_wrapper( projects.delete, ) + self.create_from_template = async_to_streamed_response_wrapper( + projects.create_from_template, + ) self.detect = async_to_streamed_response_wrapper( projects.detect, ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index ca9129a7..d1e2030f 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -19,6 +19,7 @@ from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse +from .project_create_from_template_params import ProjectCreateFromTemplateParams as ProjectCreateFromTemplateParams from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse from .organization_retrieve_permissions_response import ( OrganizationRetrievePermissionsResponse as OrganizationRetrievePermissionsResponse, diff --git a/src/codex/types/project_create_from_template_params.py b/src/codex/types/project_create_from_template_params.py new file mode 100644 index 00000000..fb1fa580 --- /dev/null +++ b/src/codex/types/project_create_from_template_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ProjectCreateFromTemplateParams"] + + +class ProjectCreateFromTemplateParams(TypedDict, total=False): + organization_id: Required[str] + + template_project_id: str + + description: Optional[str] + + name: Optional[str] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 6b742b47..d83fdd1d 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -439,6 +439,51 @@ def test_path_params_delete(self, client: Codex) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_from_template(self, client: Codex) -> None: + project = client.projects.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_from_template_with_all_params(self, client: Codex) -> None: + project = client.projects.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + template_project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + description="description", + name="name", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create_from_template(self, client: Codex) -> None: + response = client.projects.with_raw_response.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create_from_template(self, client: Codex) -> None: + with client.projects.with_streaming_response.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_detect(self, client: Codex) -> None: @@ -1329,6 +1374,51 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_from_template(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_from_template_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + template_project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + description="description", + name="name", + ) + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create_from_template(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create_from_template(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.create_from_template( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectReturnSchema, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_detect(self, async_client: AsyncCodex) -> None: From 6bfa8356fb0c06d78ae494074920068a08024c17 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:24:45 +0000 Subject: [PATCH 282/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 52b3e834..a899ac74 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.30" + ".": "0.1.0-alpha.31" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 206383e1..9aee9544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.30" +version = "0.1.0-alpha.31" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index a008ac3b..f48cd030 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.30" # x-release-please-version +__version__ = "0.1.0-alpha.31" # x-release-please-version From a9ccb4f49638aadd96c6d54e284d74ebf8ed5d5a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 02:11:36 +0000 Subject: [PATCH 283/320] fix(client): close streams without requiring full consumption --- src/codex/_streaming.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py index 3af102ce..d9c4a80a 100644 --- a/src/codex/_streaming.py +++ b/src/codex/_streaming.py @@ -57,9 +57,8 @@ def __stream__(self) -> Iterator[_T]: 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: - ... + # As we might not fully consume the response stream, we need to close it explicitly + response.close() def __enter__(self) -> Self: return self @@ -121,9 +120,8 @@ async def __stream__(self) -> AsyncIterator[_T]: 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: - ... + # As we might not fully consume the response stream, we need to close it explicitly + await response.aclose() async def __aenter__(self) -> Self: return self From 23c8c4d2f394dedeb75274f7e2805ca1078fd397 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:18:09 +0000 Subject: [PATCH 284/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 5add63e3..b8e90dd9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: ef178c3ce0c31f0785212f1138ee8eee +openapi_spec_hash: 212db383b6467e2148e62041f38c5cfb config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 From c573966bcc0e46790ceb4f60fbcd777bcb312db2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 02:26:42 +0000 Subject: [PATCH 285/320] chore(internal/tests): avoid race condition with implicit client cleanup --- tests/test_client.py | 366 ++++++++++++++++++++++++------------------- 1 file changed, 202 insertions(+), 164 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 438d827a..45622f7d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -59,51 +59,49 @@ def _get_open_connections(client: Codex | AsyncCodex) -> int: class TestCodex: - client = Codex(base_url=base_url, auth_token=auth_token, _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: Codex) -> 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: Codex) -> 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: Codex) -> None: + copied = client.copy() + assert id(copied) != id(client) - copied = self.client.copy(auth_token="another My Auth Token") + copied = client.copy(auth_token="another My Auth Token") assert copied.auth_token == "another My Auth Token" - assert self.client.auth_token == "My Auth Token" + assert client.auth_token == "My Auth Token" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, client: Codex) -> 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 = Codex( @@ -138,6 +136,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 = Codex( @@ -175,13 +174,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + client.close() + + def test_copy_signature(self, client: Codex) -> 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(): @@ -192,12 +193,12 @@ def test_copy_signature(self) -> None: assert copy_param is not None, f"copy() signature is missing the {name} param" @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) -> None: + def test_copy_build_request(self, client: Codex) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -254,14 +255,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + def test_request_timeout(self, client: Codex) -> 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) @@ -274,6 +273,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: @@ -285,6 +286,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 = Codex( @@ -295,6 +298,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 = Codex( @@ -305,6 +310,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: @@ -316,14 +323,14 @@ async def test_invalid_http_client(self) -> None: ) def test_default_headers_option(self) -> None: - client = Codex( + test_client = Codex( base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = Codex( + test_client2 = Codex( base_url=base_url, auth_token=auth_token, _strict_response_validation=True, @@ -332,10 +339,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 = Codex( base_url=base_url, @@ -357,8 +367,10 @@ def test_default_query_option(self) -> None: url = httpx.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + client.close() + + def test_request_extra_json(self, client: Codex) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -369,7 +381,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", @@ -380,7 +392,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", @@ -391,8 +403,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: Codex) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -402,7 +414,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", @@ -413,8 +425,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: Codex) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -427,7 +439,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -441,7 +453,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -484,7 +496,7 @@ def test_multipart_repeating_array(self, client: Codex) -> None: ] @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter) -> None: + def test_basic_union_response(self, respx_mock: MockRouter, client: Codex) -> None: class Model1(BaseModel): name: str @@ -493,12 +505,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: Codex) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -509,18 +521,18 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: Codex) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -536,7 +548,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 @@ -550,6 +562,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(CODEX_BASE_URL="http://localhost:5000/from/env"): client = Codex(auth_token=auth_token, _strict_response_validation=True) @@ -565,6 +579,8 @@ def test_base_url_env(self) -> None: ) assert str(client.base_url).startswith("https://api-codex.cleanlab.ai") + client.close() + @pytest.mark.parametrize( "client", [ @@ -589,6 +605,7 @@ def test_base_url_trailing_slash(self, client: Codex) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -614,6 +631,7 @@ def test_base_url_no_trailing_slash(self, client: Codex) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -639,35 +657,36 @@ def test_absolute_request_url(self, client: Codex) -> None: ), ) assert request.url == "https://myapi.com/foo" + client.close() def test_copied_client_does_not_close_http(self) -> None: - client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) - assert not client.is_closed() + test_client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied - assert not client.is_closed() + assert not test_client.is_closed() def test_client_context_manager(self) -> None: - client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) - with client as c2: - assert c2 is client + test_client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) + with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + def test_client_response_validation_error(self, respx_mock: MockRouter, client: Codex) -> 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) @@ -689,11 +708,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False) + non_strict_client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False) - response = client.get("/foo", cast_to=Model) + response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + strict_client.close() + non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -716,9 +738,9 @@ class Model(BaseModel): ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Codex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) - + def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, client: Codex + ) -> 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) @@ -734,7 +756,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" ).__enter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(client) == 0 @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) @@ -745,7 +767,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client client.projects.with_streaming_response.create( config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" ).__enter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -857,83 +879,77 @@ def test_default_client_creation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - def test_follow_redirects(self, respx_mock: MockRouter) -> None: + def test_follow_redirects(self, respx_mock: MockRouter, client: Codex) -> 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 = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) assert response.status_code == 200 assert response.json() == {"status": "ok"} @pytest.mark.respx(base_url=base_url) - def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Codex) -> 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: - self.client.post( - "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response - ) + 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 TestAsyncCodex: - client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response(self, respx_mock: MockRouter) -> None: + async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncCodex) -> 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: AsyncCodex) -> 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: AsyncCodex) -> None: + copied = async_client.copy() + assert id(copied) != id(async_client) - copied = self.client.copy(auth_token="another My Auth Token") + copied = async_client.copy(auth_token="another My Auth Token") assert copied.auth_token == "another My Auth Token" - assert self.client.auth_token == "My Auth Token" + assert async_client.auth_token == "My Auth Token" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, async_client: AsyncCodex) -> 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 = AsyncCodex( base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) @@ -966,8 +982,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 = AsyncCodex( base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_query={"foo": "bar"} ) @@ -1003,13 +1020,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: AsyncCodex) -> 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(): @@ -1020,12 +1039,12 @@ def test_copy_signature(self) -> None: assert copy_param is not None, f"copy() signature is missing the {name} param" @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) -> None: + def test_copy_build_request(self, async_client: AsyncCodex) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = async_client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -1082,12 +1101,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - async def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + async def test_request_timeout(self, async_client: AsyncCodex) -> 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 @@ -1102,6 +1121,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: @@ -1113,6 +1134,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 = AsyncCodex( @@ -1123,6 +1146,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 = AsyncCodex( @@ -1133,6 +1158,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: @@ -1143,15 +1170,15 @@ def test_invalid_http_client(self) -> None: http_client=cast(Any, http_client), ) - def test_default_headers_option(self) -> None: - client = AsyncCodex( + async def test_default_headers_option(self) -> None: + test_client = AsyncCodex( base_url=base_url, auth_token=auth_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncCodex( + test_client2 = AsyncCodex( base_url=base_url, auth_token=auth_token, _strict_response_validation=True, @@ -1160,11 +1187,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 = AsyncCodex( base_url=base_url, auth_token=auth_token, @@ -1185,8 +1215,10 @@ def test_default_query_option(self) -> None: url = httpx.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + await client.close() + + def test_request_extra_json(self, client: Codex) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1197,7 +1229,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", @@ -1208,7 +1240,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", @@ -1219,8 +1251,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: Codex) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1230,7 +1262,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", @@ -1241,8 +1273,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: Codex) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1255,7 +1287,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1269,7 +1301,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1312,7 +1344,7 @@ def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None: ] @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter) -> None: + async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncCodex) -> None: class Model1(BaseModel): name: str @@ -1321,12 +1353,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncCodex) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1337,18 +1369,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: AsyncCodex + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -1364,11 +1398,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 = AsyncCodex( base_url="https://example.com/from_init", auth_token=auth_token, _strict_response_validation=True ) @@ -1378,7 +1412,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(CODEX_BASE_URL="http://localhost:5000/from/env"): client = AsyncCodex(auth_token=auth_token, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @@ -1393,6 +1429,8 @@ def test_base_url_env(self) -> None: ) assert str(client.base_url).startswith("https://api-codex.cleanlab.ai") + await client.close() + @pytest.mark.parametrize( "client", [ @@ -1408,7 +1446,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: + async def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1417,6 +1455,7 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1433,7 +1472,7 @@ def test_base_url_trailing_slash(self, client: AsyncCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1442,6 +1481,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1458,7 +1498,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncCodex) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncCodex) -> None: + async def test_absolute_request_url(self, client: AsyncCodex) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1467,37 +1507,37 @@ def test_absolute_request_url(self, client: AsyncCodex) -> None: ), ) assert request.url == "https://myapi.com/foo" + await client.close() async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) - assert not client.is_closed() + test_client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied await asyncio.sleep(0.2) - assert not client.is_closed() + assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) - async with client as c2: - assert c2 is client + test_client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) + async with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncCodex) -> 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) @@ -1508,7 +1548,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 @@ -1520,11 +1559,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False) + non_strict_client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=False) - response = await client.get("/foo", cast_to=Model) + response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + await strict_client.close() + await non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -1547,13 +1589,12 @@ class Model(BaseModel): ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - @pytest.mark.asyncio - async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncCodex(base_url=base_url, auth_token=auth_token, _strict_response_validation=True) - + async def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncCodex + ) -> 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("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -1566,7 +1607,7 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" ).__aenter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(async_client) == 0 @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) @@ -1577,12 +1618,11 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, await async_client.projects.with_streaming_response.create( config={}, name="name", organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e" ).__aenter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, @@ -1616,7 +1656,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_omit_retry_count_header( self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1645,7 +1684,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("codex._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_overwrite_retry_count_header( self, async_client: AsyncCodex, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1698,26 +1736,26 @@ async def test_default_client_creation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects(self, respx_mock: MockRouter) -> None: + async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncCodex) -> 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 self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + 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) -> None: + async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncCodex) -> 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 self.client.post( + await async_client.post( "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response ) From 3930735a18cbd3561186efb599668c615e563d34 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:18:00 +0000 Subject: [PATCH 286/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index b8e90dd9..84b8a0b7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 212db383b6467e2148e62041f38c5cfb +openapi_spec_hash: 406f4f54d2c48da90ff1a668d2372a7a config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 From 753f964c7b2375fea548a770a7016a27dfc95132 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 03:36:05 +0000 Subject: [PATCH 287/320] chore(internal): grammar fix (it's -> its) --- src/codex/_utils/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py index 50d59269..eec7f4a1 100644 --- a/src/codex/_utils/_utils.py +++ b/src/codex/_utils/_utils.py @@ -133,7 +133,7 @@ def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: # 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 From ed2f7680ced252dc0e5f355bcb3202abaf0b571f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:17:56 +0000 Subject: [PATCH 288/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/evals.py | 48 ++-- src/codex/types/project_create_params.py | 150 +++++++++--- src/codex/types/project_detect_params.py | 150 +++++++++--- src/codex/types/project_detect_response.py | 48 +++- src/codex/types/project_list_response.py | 150 +++++++++--- src/codex/types/project_retrieve_response.py | 150 +++++++++--- src/codex/types/project_return_schema.py | 150 +++++++++--- src/codex/types/project_update_params.py | 150 +++++++++--- src/codex/types/project_validate_response.py | 74 +++++- .../types/projects/eval_create_params.py | 26 ++- .../types/projects/eval_list_response.py | 26 ++- .../types/projects/eval_update_params.py | 56 ++++- .../query_log_list_by_group_response.py | 47 +++- .../query_log_list_groups_response.py | 47 +++- .../types/projects/query_log_list_response.py | 47 +++- .../projects/query_log_retrieve_response.py | 47 +++- ...remediation_list_resolved_logs_response.py | 47 +++- tests/api_resources/projects/test_evals.py | 36 ++- tests/api_resources/test_projects.py | 216 +++++++++++++++--- 20 files changed, 1387 insertions(+), 280 deletions(-) diff --git a/.stats.yml b/.stats.yml index 84b8a0b7..fdb7be86 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 406f4f54d2c48da90ff1a668d2372a7a +openapi_spec_hash: 80b1836ebec22fc0dc25d5d8efe62a50 config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 diff --git a/src/codex/resources/projects/evals.py b/src/codex/resources/projects/evals.py index f732981f..dd535575 100644 --- a/src/codex/resources/projects/evals.py +++ b/src/codex/resources/projects/evals.py @@ -54,7 +54,7 @@ def create( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_create_params.GuardrailedFallback] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -87,8 +87,7 @@ def create( enabled: Allows the evaluation to be disabled without removing it - guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be - guardrailed + guardrailed_fallback: message, priority, type is_default: Whether the eval is a default, built-in eval or a custom eval @@ -128,7 +127,7 @@ def create( "name": name, "context_identifier": context_identifier, "enabled": enabled, - "guardrailed_fallback_message": guardrailed_fallback_message, + "guardrailed_fallback": guardrailed_fallback, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, @@ -157,7 +156,8 @@ def update( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_update_params.CustomEvalCreateOrUpdateSchemaGuardrailedFallback] + | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -190,8 +190,7 @@ def update( enabled: Allows the evaluation to be disabled without removing it - guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be - guardrailed + guardrailed_fallback: message, priority, type is_default: Whether the eval is a default, built-in eval or a custom eval @@ -230,7 +229,7 @@ def update( project_id: str, body_eval_key: str, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_update_params.DefaultEvalUpdateSchemaGuardrailedFallback] | Omit = omit, priority: Optional[int] | Omit = omit, should_escalate: bool | Omit = omit, should_guardrail: bool | Omit = omit, @@ -252,8 +251,7 @@ def update( enabled: Allows the evaluation to be disabled without removing it - guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be - guardrailed + guardrailed_fallback: message, priority, type priority: Priority order for evals (lower number = higher priority) to determine primary eval issue to surface @@ -288,7 +286,9 @@ def update( name: str | Omit = omit, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_update_params.CustomEvalCreateOrUpdateSchemaGuardrailedFallback] + | Optional[eval_update_params.DefaultEvalUpdateSchemaGuardrailedFallback] + | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -317,7 +317,7 @@ def update( "name": name, "context_identifier": context_identifier, "enabled": enabled, - "guardrailed_fallback_message": guardrailed_fallback_message, + "guardrailed_fallback": guardrailed_fallback, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, @@ -448,7 +448,7 @@ async def create( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_create_params.GuardrailedFallback] | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -481,8 +481,7 @@ async def create( enabled: Allows the evaluation to be disabled without removing it - guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be - guardrailed + guardrailed_fallback: message, priority, type is_default: Whether the eval is a default, built-in eval or a custom eval @@ -522,7 +521,7 @@ async def create( "name": name, "context_identifier": context_identifier, "enabled": enabled, - "guardrailed_fallback_message": guardrailed_fallback_message, + "guardrailed_fallback": guardrailed_fallback, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, @@ -551,7 +550,8 @@ async def update( name: str, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_update_params.CustomEvalCreateOrUpdateSchemaGuardrailedFallback] + | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -584,8 +584,7 @@ async def update( enabled: Allows the evaluation to be disabled without removing it - guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be - guardrailed + guardrailed_fallback: message, priority, type is_default: Whether the eval is a default, built-in eval or a custom eval @@ -624,7 +623,7 @@ async def update( project_id: str, body_eval_key: str, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_update_params.DefaultEvalUpdateSchemaGuardrailedFallback] | Omit = omit, priority: Optional[int] | Omit = omit, should_escalate: bool | Omit = omit, should_guardrail: bool | Omit = omit, @@ -646,8 +645,7 @@ async def update( enabled: Allows the evaluation to be disabled without removing it - guardrailed_fallback_message: Fallback message to use if this eval fails and causes the response to be - guardrailed + guardrailed_fallback: message, priority, type priority: Priority order for evals (lower number = higher priority) to determine primary eval issue to surface @@ -682,7 +680,9 @@ async def update( name: str | Omit = omit, context_identifier: Optional[str] | Omit = omit, enabled: bool | Omit = omit, - guardrailed_fallback_message: Optional[str] | Omit = omit, + guardrailed_fallback: Optional[eval_update_params.CustomEvalCreateOrUpdateSchemaGuardrailedFallback] + | Optional[eval_update_params.DefaultEvalUpdateSchemaGuardrailedFallback] + | Omit = omit, is_default: bool | Omit = omit, priority: Optional[int] | Omit = omit, query_identifier: Optional[str] | Omit = omit, @@ -711,7 +711,7 @@ async def update( "name": name, "context_identifier": context_identifier, "enabled": enabled, - "guardrailed_fallback_message": guardrailed_fallback_message, + "guardrailed_fallback": guardrailed_fallback, "is_default": is_default, "priority": priority, "query_identifier": query_identifier, diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index bd14d3dd..4704f638 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -11,12 +11,18 @@ "ConfigEvalConfig", "ConfigEvalConfigCustomEvals", "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback", "ConfigEvalConfigDefaultEvals", "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback", "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsTrustworthiness", + "ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback", ] @@ -32,6 +38,23 @@ class ProjectCreateParams(TypedDict, total=False): description: Optional[str] +class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): criteria: Required[str] """ @@ -57,11 +80,8 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback] + """message, priority, type""" is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -103,6 +123,23 @@ class ConfigEvalConfigCustomEvals(TypedDict, total=False): evals: Dict[str, ConfigEvalConfigCustomEvalsEvals] +class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): eval_key: Required[str] """ @@ -116,11 +153,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -144,6 +178,23 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): eval_key: Required[str] """ @@ -157,11 +208,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -185,6 +233,23 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): eval_key: Required[str] """ @@ -198,11 +263,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -226,6 +288,23 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): eval_key: Required[str] """ @@ -239,11 +318,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -267,6 +343,23 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): eval_key: Required[str] """ @@ -280,11 +373,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ diff --git a/src/codex/types/project_detect_params.py b/src/codex/types/project_detect_params.py index f29d3e00..8e93971b 100644 --- a/src/codex/types/project_detect_params.py +++ b/src/codex/types/project_detect_params.py @@ -31,12 +31,18 @@ "EvalConfig", "EvalConfigCustomEvals", "EvalConfigCustomEvalsEvals", + "EvalConfigCustomEvalsEvalsGuardrailedFallback", "EvalConfigDefaultEvals", "EvalConfigDefaultEvalsContextSufficiency", + "EvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback", "EvalConfigDefaultEvalsQueryEase", + "EvalConfigDefaultEvalsQueryEaseGuardrailedFallback", "EvalConfigDefaultEvalsResponseGroundedness", + "EvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback", "EvalConfigDefaultEvalsResponseHelpfulness", + "EvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback", "EvalConfigDefaultEvalsTrustworthiness", + "EvalConfigDefaultEvalsTrustworthinessGuardrailedFallback", "Message", "MessageChatCompletionAssistantMessageParamInput", "MessageChatCompletionAssistantMessageParamInputAudio", @@ -433,6 +439,23 @@ class ResponseChatCompletionTyped(TypedDict, total=False): Response: TypeAlias = Union[str, ResponseChatCompletion] +class EvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class EvalConfigCustomEvalsEvals(TypedDict, total=False): criteria: Required[str] """ @@ -458,11 +481,8 @@ class EvalConfigCustomEvalsEvals(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[EvalConfigCustomEvalsEvalsGuardrailedFallback] + """message, priority, type""" is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -504,6 +524,23 @@ class EvalConfigCustomEvals(TypedDict, total=False): evals: Dict[str, EvalConfigCustomEvalsEvals] +class EvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class EvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): eval_key: Required[str] """ @@ -517,11 +554,8 @@ class EvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[EvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -545,6 +579,23 @@ class EvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class EvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class EvalConfigDefaultEvalsQueryEase(TypedDict, total=False): eval_key: Required[str] """ @@ -558,11 +609,8 @@ class EvalConfigDefaultEvalsQueryEase(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[EvalConfigDefaultEvalsQueryEaseGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -586,6 +634,23 @@ class EvalConfigDefaultEvalsQueryEase(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class EvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class EvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): eval_key: Required[str] """ @@ -599,11 +664,8 @@ class EvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[EvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -627,6 +689,23 @@ class EvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class EvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class EvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): eval_key: Required[str] """ @@ -640,11 +719,8 @@ class EvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[EvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -668,6 +744,23 @@ class EvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class EvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class EvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): eval_key: Required[str] """ @@ -681,11 +774,8 @@ class EvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[EvalConfigDefaultEvalsTrustworthinessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ diff --git a/src/codex/types/project_detect_response.py b/src/codex/types/project_detect_response.py index 4e4b74f7..df03c864 100644 --- a/src/codex/types/project_detect_response.py +++ b/src/codex/types/project_detect_response.py @@ -1,14 +1,32 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from typing_extensions import Literal from .._models import BaseModel -__all__ = ["ProjectDetectResponse", "EvalScores"] +__all__ = ["ProjectDetectResponse", "EvalScores", "EvalScoresGuardrailedFallback", "GuardrailedFallback"] + + +class EvalScoresGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" class EvalScores(BaseModel): - guardrailed_fallback_message: Optional[str] = None + guardrailed_fallback: Optional[EvalScoresGuardrailedFallback] = None score: Optional[float] = None @@ -21,6 +39,26 @@ class EvalScores(BaseModel): log: Optional[object] = None +class GuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + guardrail_name: Optional[str] = None + """Name of the guardrail that triggered the fallback""" + + class ProjectDetectResponse(BaseModel): escalated_to_sme: bool """ @@ -47,6 +85,12 @@ class ProjectDetectResponse(BaseModel): expert review. Expert review will override the original guardrail decision. """ + guardrailed_fallback: Optional[GuardrailedFallback] = None + """ + Name, fallback message, fallback priority, and fallback type of the triggered + guardrail with the highest fallback priority + """ + should_guardrail: bool """ True if the response should be guardrailed by the AI system, False if the diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index 66d2037f..e4ce5585 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -13,16 +13,39 @@ "ProjectConfigEvalConfig", "ProjectConfigEvalConfigCustomEvals", "ProjectConfigEvalConfigCustomEvalsEvals", + "ProjectConfigEvalConfigCustomEvalsEvalsGuardrailedFallback", "ProjectConfigEvalConfigDefaultEvals", "ProjectConfigEvalConfigDefaultEvalsContextSufficiency", + "ProjectConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback", "ProjectConfigEvalConfigDefaultEvalsQueryEase", + "ProjectConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback", "ProjectConfigEvalConfigDefaultEvalsResponseGroundedness", + "ProjectConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback", "ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ProjectConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback", "ProjectConfigEvalConfigDefaultEvalsTrustworthiness", + "ProjectConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback", "Filters", ] +class ProjectConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): criteria: str """ @@ -54,11 +77,8 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ProjectConfigEvalConfigCustomEvalsEvalsGuardrailedFallback] = None + """message, priority, type""" is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" @@ -100,6 +120,23 @@ class ProjectConfigEvalConfigCustomEvals(BaseModel): evals: Optional[Dict[str, ProjectConfigEvalConfigCustomEvalsEvals]] = None +class ProjectConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): display_name: str """Human-friendly name for display. @@ -119,11 +156,8 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ProjectConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -147,6 +181,23 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ProjectConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): display_name: str """Human-friendly name for display. @@ -166,11 +217,8 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ProjectConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -194,6 +242,23 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ProjectConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): display_name: str """Human-friendly name for display. @@ -213,11 +278,8 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ProjectConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -241,6 +303,23 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): display_name: str """Human-friendly name for display. @@ -260,11 +339,8 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ProjectConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -288,6 +364,23 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ProjectConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): display_name: str """Human-friendly name for display. @@ -307,11 +400,8 @@ class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ProjectConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 694f590b..8fe77415 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -12,15 +12,38 @@ "ConfigEvalConfig", "ConfigEvalConfigCustomEvals", "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback", "ConfigEvalConfigDefaultEvals", "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback", "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsTrustworthiness", + "ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback", ] +class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigCustomEvalsEvals(BaseModel): criteria: str """ @@ -52,11 +75,8 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback] = None + """message, priority, type""" is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" @@ -98,6 +118,23 @@ class ConfigEvalConfigCustomEvals(BaseModel): evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None +class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): display_name: str """Human-friendly name for display. @@ -117,11 +154,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -145,6 +179,23 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): display_name: str """Human-friendly name for display. @@ -164,11 +215,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -192,6 +240,23 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): display_name: str """Human-friendly name for display. @@ -211,11 +276,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -239,6 +301,23 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): display_name: str """Human-friendly name for display. @@ -258,11 +337,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -286,6 +362,23 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): display_name: str """Human-friendly name for display. @@ -305,11 +398,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index b8b54044..423d0ce2 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -12,15 +12,38 @@ "ConfigEvalConfig", "ConfigEvalConfigCustomEvals", "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback", "ConfigEvalConfigDefaultEvals", "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback", "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsTrustworthiness", + "ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback", ] +class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigCustomEvalsEvals(BaseModel): criteria: str """ @@ -52,11 +75,8 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback] = None + """message, priority, type""" is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" @@ -98,6 +118,23 @@ class ConfigEvalConfigCustomEvals(BaseModel): evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None +class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): display_name: str """Human-friendly name for display. @@ -117,11 +154,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -145,6 +179,23 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): display_name: str """Human-friendly name for display. @@ -164,11 +215,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -192,6 +240,23 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): display_name: str """Human-friendly name for display. @@ -211,11 +276,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -239,6 +301,23 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): display_name: str """Human-friendly name for display. @@ -258,11 +337,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ @@ -286,6 +362,23 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): display_name: str """Human-friendly name for display. @@ -305,11 +398,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback] = None + """message, priority, type""" priority: Optional[int] = None """ diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 4ca5abfc..3557c2d0 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -11,12 +11,18 @@ "ConfigEvalConfig", "ConfigEvalConfigCustomEvals", "ConfigEvalConfigCustomEvalsEvals", + "ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback", "ConfigEvalConfigDefaultEvals", "ConfigEvalConfigDefaultEvalsContextSufficiency", + "ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback", "ConfigEvalConfigDefaultEvalsQueryEase", + "ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseGroundedness", + "ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsResponseHelpfulness", + "ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback", "ConfigEvalConfigDefaultEvalsTrustworthiness", + "ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback", ] @@ -30,6 +36,23 @@ class ProjectUpdateParams(TypedDict, total=False): name: Optional[str] +class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): criteria: Required[str] """ @@ -55,11 +78,8 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback] + """message, priority, type""" is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -101,6 +121,23 @@ class ConfigEvalConfigCustomEvals(TypedDict, total=False): evals: Dict[str, ConfigEvalConfigCustomEvalsEvals] +class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): eval_key: Required[str] """ @@ -114,11 +151,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -142,6 +176,23 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): eval_key: Required[str] """ @@ -155,11 +206,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -183,6 +231,23 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): eval_key: Required[str] """ @@ -196,11 +261,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -224,6 +286,23 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): eval_key: Required[str] """ @@ -237,11 +316,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -265,6 +341,23 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): eval_key: Required[str] """ @@ -278,11 +371,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index 458e4fc5..b9166c20 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -1,10 +1,35 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Optional +from typing_extensions import Literal from .._models import BaseModel -__all__ = ["ProjectValidateResponse", "DeterministicGuardrailsResults", "EvalScores"] +__all__ = [ + "ProjectValidateResponse", + "DeterministicGuardrailsResults", + "DeterministicGuardrailsResultsGuardrailedFallback", + "EvalScores", + "EvalScoresGuardrailedFallback", + "GuardrailedFallback", +] + + +class DeterministicGuardrailsResultsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" class DeterministicGuardrailsResults(BaseModel): @@ -12,13 +37,30 @@ class DeterministicGuardrailsResults(BaseModel): should_guardrail: bool - fallback_message: Optional[str] = None + guardrailed_fallback: Optional[DeterministicGuardrailsResultsGuardrailedFallback] = None matches: Optional[List[str]] = None +class EvalScoresGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class EvalScores(BaseModel): - guardrailed_fallback_message: Optional[str] = None + guardrailed_fallback: Optional[EvalScoresGuardrailedFallback] = None score: Optional[float] = None @@ -31,6 +73,26 @@ class EvalScores(BaseModel): log: Optional[object] = None +class GuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + guardrail_name: Optional[str] = None + """Name of the guardrail that triggered the fallback""" + + class ProjectValidateResponse(BaseModel): deterministic_guardrails_results: Optional[Dict[str, DeterministicGuardrailsResults]] = None """Results from deterministic guardrails applied to the response.""" @@ -60,6 +122,12 @@ class ProjectValidateResponse(BaseModel): expert review. Expert review will override the original guardrail decision. """ + guardrailed_fallback: Optional[GuardrailedFallback] = None + """ + Name, fallback message, fallback priority, and fallback type of the triggered + guardrail with the highest fallback priority + """ + log_id: str """The UUID of the query log entry created for this validation request.""" diff --git a/src/codex/types/projects/eval_create_params.py b/src/codex/types/projects/eval_create_params.py index 5f66f6ad..d4ec41e6 100644 --- a/src/codex/types/projects/eval_create_params.py +++ b/src/codex/types/projects/eval_create_params.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Literal, Required, TypedDict -__all__ = ["EvalCreateParams"] +__all__ = ["EvalCreateParams", "GuardrailedFallback"] class EvalCreateParams(TypedDict, total=False): @@ -33,11 +33,8 @@ class EvalCreateParams(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[GuardrailedFallback] + """message, priority, type""" is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -73,3 +70,20 @@ class EvalCreateParams(TypedDict, total=False): threshold_direction: Literal["above", "below"] """Whether the evaluation fails when score is above or below the threshold""" + + +class GuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py index 572de97b..2aa0d755 100644 --- a/src/codex/types/projects/eval_list_response.py +++ b/src/codex/types/projects/eval_list_response.py @@ -5,7 +5,24 @@ from ..._models import BaseModel -__all__ = ["EvalListResponse", "Eval"] +__all__ = ["EvalListResponse", "Eval", "EvalGuardrailedFallback"] + + +class EvalGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" class Eval(BaseModel): @@ -39,11 +56,8 @@ class Eval(BaseModel): enabled: Optional[bool] = None """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] = None - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[EvalGuardrailedFallback] = None + """message, priority, type""" is_default: Optional[bool] = None """Whether the eval is a default, built-in eval or a custom eval""" diff --git a/src/codex/types/projects/eval_update_params.py b/src/codex/types/projects/eval_update_params.py index 1cfa8360..7da4e1ee 100644 --- a/src/codex/types/projects/eval_update_params.py +++ b/src/codex/types/projects/eval_update_params.py @@ -7,7 +7,13 @@ from ..._utils import PropertyInfo -__all__ = ["EvalUpdateParams", "CustomEvalCreateOrUpdateSchema", "DefaultEvalUpdateSchema"] +__all__ = [ + "EvalUpdateParams", + "CustomEvalCreateOrUpdateSchema", + "CustomEvalCreateOrUpdateSchemaGuardrailedFallback", + "DefaultEvalUpdateSchema", + "DefaultEvalUpdateSchemaGuardrailedFallback", +] class CustomEvalCreateOrUpdateSchema(TypedDict, total=False): @@ -37,11 +43,8 @@ class CustomEvalCreateOrUpdateSchema(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[CustomEvalCreateOrUpdateSchemaGuardrailedFallback] + """message, priority, type""" is_default: bool """Whether the eval is a default, built-in eval or a custom eval""" @@ -79,6 +82,23 @@ class CustomEvalCreateOrUpdateSchema(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class CustomEvalCreateOrUpdateSchemaGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + class DefaultEvalUpdateSchema(TypedDict, total=False): project_id: Required[str] @@ -91,11 +111,8 @@ class DefaultEvalUpdateSchema(TypedDict, total=False): enabled: bool """Allows the evaluation to be disabled without removing it""" - guardrailed_fallback_message: Optional[str] - """ - Fallback message to use if this eval fails and causes the response to be - guardrailed - """ + guardrailed_fallback: Optional[DefaultEvalUpdateSchemaGuardrailedFallback] + """message, priority, type""" priority: Optional[int] """ @@ -119,4 +136,21 @@ class DefaultEvalUpdateSchema(TypedDict, total=False): """Whether the evaluation fails when score is above or below the threshold""" +class DefaultEvalUpdateSchemaGuardrailedFallback(TypedDict, total=False): + message: Required[str] + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: Required[int] + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Required[Literal["ai_guidance", "expert_answer"]] + """Type of fallback to use if response is guardrailed""" + + EvalUpdateParams: TypeAlias = Union[CustomEvalCreateOrUpdateSchema, DefaultEvalUpdateSchema] diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 5df928b6..0aa74bf4 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -16,8 +16,10 @@ "QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores", "QueryLogsByGroupQueryLogContext", "QueryLogsByGroupQueryLogDeterministicGuardrailsResults", + "QueryLogsByGroupQueryLogDeterministicGuardrailsResultsGuardrailedFallback", "QueryLogsByGroupQueryLogEvaluatedResponseToolCall", "QueryLogsByGroupQueryLogEvaluatedResponseToolCallFunction", + "QueryLogsByGroupQueryLogGuardrailedFallback", "QueryLogsByGroupQueryLogMessage", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -98,12 +100,29 @@ class QueryLogsByGroupQueryLogContext(BaseModel): """Title or heading of the document. Useful for display and context.""" +class QueryLogsByGroupQueryLogDeterministicGuardrailsResultsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class QueryLogsByGroupQueryLogDeterministicGuardrailsResults(BaseModel): guardrail_name: str should_guardrail: bool - fallback_message: Optional[str] = None + guardrailed_fallback: Optional[QueryLogsByGroupQueryLogDeterministicGuardrailsResultsGuardrailedFallback] = None matches: Optional[List[str]] = None @@ -122,6 +141,26 @@ class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel): type: Literal["function"] +class QueryLogsByGroupQueryLogGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + guardrail_name: Optional[str] = None + """Name of the guardrail that triggered the fallback""" + + class QueryLogsByGroupQueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -446,6 +485,12 @@ class QueryLogsByGroupQueryLog(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + guardrailed_fallback: Optional[QueryLogsByGroupQueryLogGuardrailedFallback] = None + """ + Name, fallback message, priority, and type for for the triggered guardrail with + the highest priority + """ + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 727cac2a..e829f736 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -14,8 +14,10 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "DeterministicGuardrailsResultsGuardrailedFallback", "EvaluatedResponseToolCall", "EvaluatedResponseToolCallFunction", + "GuardrailedFallback", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -95,12 +97,29 @@ class Context(BaseModel): """Title or heading of the document. Useful for display and context.""" +class DeterministicGuardrailsResultsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class DeterministicGuardrailsResults(BaseModel): guardrail_name: str should_guardrail: bool - fallback_message: Optional[str] = None + guardrailed_fallback: Optional[DeterministicGuardrailsResultsGuardrailedFallback] = None matches: Optional[List[str]] = None @@ -119,6 +138,26 @@ class EvaluatedResponseToolCall(BaseModel): type: Literal["function"] +class GuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + guardrail_name: Optional[str] = None + """Name of the guardrail that triggered the fallback""" + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -441,6 +480,12 @@ class QueryLogListGroupsResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + guardrailed_fallback: Optional[GuardrailedFallback] = None + """ + Name, fallback message, priority, and type for for the triggered guardrail with + the highest priority + """ + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 0a8b4275..b26272eb 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -14,8 +14,10 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "DeterministicGuardrailsResultsGuardrailedFallback", "EvaluatedResponseToolCall", "EvaluatedResponseToolCallFunction", + "GuardrailedFallback", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -95,12 +97,29 @@ class Context(BaseModel): """Title or heading of the document. Useful for display and context.""" +class DeterministicGuardrailsResultsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class DeterministicGuardrailsResults(BaseModel): guardrail_name: str should_guardrail: bool - fallback_message: Optional[str] = None + guardrailed_fallback: Optional[DeterministicGuardrailsResultsGuardrailedFallback] = None matches: Optional[List[str]] = None @@ -119,6 +138,26 @@ class EvaluatedResponseToolCall(BaseModel): type: Literal["function"] +class GuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + guardrail_name: Optional[str] = None + """Name of the guardrail that triggered the fallback""" + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -429,6 +468,12 @@ class QueryLogListResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + guardrailed_fallback: Optional[GuardrailedFallback] = None + """ + Name, fallback message, priority, and type for for the triggered guardrail with + the highest priority + """ + messages: Optional[List[Message]] = None """Message history to provide conversation context for the query. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 13510f87..0ef986f2 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -14,8 +14,10 @@ "FormattedNonGuardrailEvalScores", "Context", "DeterministicGuardrailsResults", + "DeterministicGuardrailsResultsGuardrailedFallback", "EvaluatedResponseToolCall", "EvaluatedResponseToolCallFunction", + "GuardrailedFallback", "Message", "MessageChatCompletionAssistantMessageParamOutput", "MessageChatCompletionAssistantMessageParamOutputAudio", @@ -95,12 +97,29 @@ class Context(BaseModel): """Title or heading of the document. Useful for display and context.""" +class DeterministicGuardrailsResultsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class DeterministicGuardrailsResults(BaseModel): guardrail_name: str should_guardrail: bool - fallback_message: Optional[str] = None + guardrailed_fallback: Optional[DeterministicGuardrailsResultsGuardrailedFallback] = None matches: Optional[List[str]] = None @@ -119,6 +138,26 @@ class EvaluatedResponseToolCall(BaseModel): type: Literal["function"] +class GuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + guardrail_name: Optional[str] = None + """Name of the guardrail that triggered the fallback""" + + class MessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -433,6 +472,12 @@ class QueryLogRetrieveResponse(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + guardrailed_fallback: Optional[GuardrailedFallback] = None + """ + Name, fallback message, priority, and type for for the triggered guardrail with + the highest priority + """ + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 16017b45..e04e4130 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -15,8 +15,10 @@ "QueryLogFormattedNonGuardrailEvalScores", "QueryLogContext", "QueryLogDeterministicGuardrailsResults", + "QueryLogDeterministicGuardrailsResultsGuardrailedFallback", "QueryLogEvaluatedResponseToolCall", "QueryLogEvaluatedResponseToolCallFunction", + "QueryLogGuardrailedFallback", "QueryLogMessage", "QueryLogMessageChatCompletionAssistantMessageParamOutput", "QueryLogMessageChatCompletionAssistantMessageParamOutputAudio", @@ -96,12 +98,29 @@ class QueryLogContext(BaseModel): """Title or heading of the document. Useful for display and context.""" +class QueryLogDeterministicGuardrailsResultsGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + class QueryLogDeterministicGuardrailsResults(BaseModel): guardrail_name: str should_guardrail: bool - fallback_message: Optional[str] = None + guardrailed_fallback: Optional[QueryLogDeterministicGuardrailsResultsGuardrailedFallback] = None matches: Optional[List[str]] = None @@ -120,6 +139,26 @@ class QueryLogEvaluatedResponseToolCall(BaseModel): type: Literal["function"] +class QueryLogGuardrailedFallback(BaseModel): + message: str + """ + Fallback message to use if this eval fails and causes the response to be + guardrailed + """ + + priority: int + """ + Priority order for guardrails (lower number = higher priority) to determine + which fallback to use if multiple guardrails are triggered + """ + + type: Literal["ai_guidance", "expert_answer"] + """Type of fallback to use if response is guardrailed""" + + guardrail_name: Optional[str] = None + """Name of the guardrail that triggered the fallback""" + + class QueryLogMessageChatCompletionAssistantMessageParamOutputAudio(BaseModel): id: str @@ -436,6 +475,12 @@ class QueryLog(BaseModel): guardrailed: Optional[bool] = None """If true, the response was guardrailed""" + guardrailed_fallback: Optional[QueryLogGuardrailedFallback] = None + """ + Name, fallback message, priority, and type for for the triggered guardrail with + the highest priority + """ + messages: Optional[List[QueryLogMessage]] = None """Message history to provide conversation context for the query. diff --git a/tests/api_resources/projects/test_evals.py b/tests/api_resources/projects/test_evals.py index 1ccde4f3..4fb6a6ba 100644 --- a/tests/api_resources/projects/test_evals.py +++ b/tests/api_resources/projects/test_evals.py @@ -39,7 +39,11 @@ def test_method_create_with_all_params(self, client: Codex) -> None: name="name", context_identifier="context_identifier", enabled=True, - guardrailed_fallback_message="guardrailed_fallback_message", + guardrailed_fallback={ + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, is_default=True, priority=0, query_identifier="query_identifier", @@ -117,7 +121,11 @@ def test_method_update_with_all_params_overload_1(self, client: Codex) -> None: name="name", context_identifier="context_identifier", enabled=True, - guardrailed_fallback_message="guardrailed_fallback_message", + guardrailed_fallback={ + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, is_default=True, priority=0, query_identifier="query_identifier", @@ -202,7 +210,11 @@ def test_method_update_with_all_params_overload_2(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", body_eval_key="eval_key", enabled=True, - guardrailed_fallback_message="guardrailed_fallback_message", + guardrailed_fallback={ + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, priority=0, should_escalate=True, should_guardrail=True, @@ -390,7 +402,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> name="name", context_identifier="context_identifier", enabled=True, - guardrailed_fallback_message="guardrailed_fallback_message", + guardrailed_fallback={ + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, is_default=True, priority=0, query_identifier="query_identifier", @@ -468,7 +484,11 @@ async def test_method_update_with_all_params_overload_1(self, async_client: Asyn name="name", context_identifier="context_identifier", enabled=True, - guardrailed_fallback_message="guardrailed_fallback_message", + guardrailed_fallback={ + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, is_default=True, priority=0, query_identifier="query_identifier", @@ -553,7 +573,11 @@ async def test_method_update_with_all_params_overload_2(self, async_client: Asyn project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", body_eval_key="eval_key", enabled=True, - guardrailed_fallback_message="guardrailed_fallback_message", + guardrailed_fallback={ + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, priority=0, should_escalate=True, should_guardrail=True, diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index d83fdd1d..97f973c1 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -51,7 +51,11 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "name": "name", "context_identifier": "context_identifier", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -68,7 +72,11 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -79,7 +87,11 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -90,7 +102,11 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -101,7 +117,11 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -112,7 +132,11 @@ def test_method_create_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -236,7 +260,11 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "name": "name", "context_identifier": "context_identifier", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -253,7 +281,11 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -264,7 +296,11 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -275,7 +311,11 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -286,7 +326,11 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -297,7 +341,11 @@ def test_method_update_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -513,7 +561,11 @@ def test_method_detect_with_all_params(self, client: Codex) -> None: "name": "name", "context_identifier": "context_identifier", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -530,7 +582,11 @@ def test_method_detect_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -541,7 +597,11 @@ def test_method_detect_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -552,7 +612,11 @@ def test_method_detect_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -563,7 +627,11 @@ def test_method_detect_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -574,7 +642,11 @@ def test_method_detect_with_all_params(self, client: Codex) -> None: "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -986,7 +1058,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "name": "name", "context_identifier": "context_identifier", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -1003,7 +1079,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1014,7 +1094,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1025,7 +1109,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1036,7 +1124,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1047,7 +1139,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1171,7 +1267,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "name": "name", "context_identifier": "context_identifier", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -1188,7 +1288,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1199,7 +1303,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1210,7 +1318,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1221,7 +1333,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1232,7 +1348,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1448,7 +1568,11 @@ async def test_method_detect_with_all_params(self, async_client: AsyncCodex) -> "name": "name", "context_identifier": "context_identifier", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "is_default": True, "priority": 0, "query_identifier": "query_identifier", @@ -1465,7 +1589,11 @@ async def test_method_detect_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1476,7 +1604,11 @@ async def test_method_detect_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1487,7 +1619,11 @@ async def test_method_detect_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1498,7 +1634,11 @@ async def test_method_detect_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, @@ -1509,7 +1649,11 @@ async def test_method_detect_with_all_params(self, async_client: AsyncCodex) -> "eval_key": "eval_key", "name": "name", "enabled": True, - "guardrailed_fallback_message": "guardrailed_fallback_message", + "guardrailed_fallback": { + "message": "message", + "priority": 0, + "type": "ai_guidance", + }, "priority": 0, "should_escalate": True, "should_guardrail": True, From 3313348a9bdf09e4cf64b91d81f85674dec0fd07 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 01:16:31 +0000 Subject: [PATCH 289/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a899ac74..2ce25fec 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.31" + ".": "0.1.0-alpha.32" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9aee9544..df348502 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.31" +version = "0.1.0-alpha.32" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index f48cd030..57e8ea7c 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.31" # x-release-please-version +__version__ = "0.1.0-alpha.32" # x-release-please-version From 39b7a95600bf0b3f2ed6bafffecc733f099e6107 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:17:55 +0000 Subject: [PATCH 290/320] feat(api): api update --- .stats.yml | 2 +- src/codex/types/projects/query_log_list_by_group_response.py | 5 +++++ src/codex/types/projects/query_log_list_groups_response.py | 5 +++++ src/codex/types/projects/query_log_list_response.py | 5 +++++ src/codex/types/projects/query_log_retrieve_response.py | 5 +++++ .../projects/remediation_list_resolved_logs_response.py | 5 +++++ 6 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index fdb7be86..a8db3516 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 80b1836ebec22fc0dc25d5d8efe62a50 +openapi_spec_hash: 0f9ec74cbe1e9972d20ea57aded46fb9 config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 0aa74bf4..2877e084 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -416,6 +416,9 @@ class QueryLogsByGroupQueryLog(BaseModel): ai_guidance_id: Optional[str] = None """ID of the AI guidance remediation that was created from this query log.""" + applied_expert_answer_id: Optional[str] = None + """ID of the expert answer that was applied to the query.""" + context: Optional[List[QueryLogsByGroupQueryLogContext]] = None """RAG context used for the query""" @@ -491,6 +494,8 @@ class QueryLogsByGroupQueryLog(BaseModel): the highest priority """ + issue_id: Optional[str] = None + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index e829f736..be46259b 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -411,6 +411,9 @@ class QueryLogListGroupsResponse(BaseModel): ai_guidance_id: Optional[str] = None """ID of the AI guidance remediation that was created from this query log.""" + applied_expert_answer_id: Optional[str] = None + """ID of the expert answer that was applied to the query.""" + context: Optional[List[Context]] = None """RAG context used for the query""" @@ -486,6 +489,8 @@ class QueryLogListGroupsResponse(BaseModel): the highest priority """ + issue_id: Optional[str] = None + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index b26272eb..7558ae34 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -399,6 +399,9 @@ class QueryLogListResponse(BaseModel): ai_guidance_id: Optional[str] = None """ID of the AI guidance remediation that was created from this query log.""" + applied_expert_answer_id: Optional[str] = None + """ID of the expert answer that was applied to the query.""" + context: Optional[List[Context]] = None """RAG context used for the query""" @@ -474,6 +477,8 @@ class QueryLogListResponse(BaseModel): the highest priority """ + issue_id: Optional[str] = None + messages: Optional[List[Message]] = None """Message history to provide conversation context for the query. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 0ef986f2..8851069a 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -403,6 +403,9 @@ class QueryLogRetrieveResponse(BaseModel): ai_guidance_id: Optional[str] = None """ID of the AI guidance remediation that was created from this query log.""" + applied_expert_answer_id: Optional[str] = None + """ID of the expert answer that was applied to the query.""" + context: Optional[List[Context]] = None """RAG context used for the query""" @@ -478,6 +481,8 @@ class QueryLogRetrieveResponse(BaseModel): the highest priority """ + issue_id: Optional[str] = None + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index e04e4130..5ed3c015 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -406,6 +406,9 @@ class QueryLog(BaseModel): ai_guidance_id: Optional[str] = None """ID of the AI guidance remediation that was created from this query log.""" + applied_expert_answer_id: Optional[str] = None + """ID of the expert answer that was applied to the query.""" + context: Optional[List[QueryLogContext]] = None """RAG context used for the query""" @@ -481,6 +484,8 @@ class QueryLog(BaseModel): the highest priority """ + issue_id: Optional[str] = None + messages: Optional[List[QueryLogMessage]] = None """Message history to provide conversation context for the query. From 73381daf883847187ad77c6f6d8fc4bdd6919f3d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 03:32:11 +0000 Subject: [PATCH 291/320] chore(package): drop Python 3.8 support --- README.md | 4 ++-- pyproject.toml | 5 ++--- src/codex/_utils/_sync.py | 34 +++------------------------------- 3 files changed, 7 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 7d673351..42ac4876 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![PyPI version](https://img.shields.io/pypi/v/codex-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/codex-sdk/) -The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+ +The Codex SDK library provides convenient access to the Codex REST API from any Python 3.9+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). @@ -471,7 +471,7 @@ print(codex.__version__) ## Requirements -Python 3.8 or higher. +Python 3.9 or higher. ## Contributing diff --git a/pyproject.toml b/pyproject.toml index df348502..5aaf2dfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,11 +15,10 @@ dependencies = [ "distro>=1.7.0, <2", "sniffio", ] -requires-python = ">= 3.8" +requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -141,7 +140,7 @@ filterwarnings = [ # there are a couple of flags that are still disabled by # default in strict mode as they are experimental and niche. typeCheckingMode = "strict" -pythonVersion = "3.8" +pythonVersion = "3.9" exclude = [ "_dev", diff --git a/src/codex/_utils/_sync.py b/src/codex/_utils/_sync.py index ad7ec71b..f6027c18 100644 --- a/src/codex/_utils/_sync.py +++ b/src/codex/_utils/_sync.py @@ -1,10 +1,8 @@ from __future__ import annotations -import sys import asyncio import functools -import contextvars -from typing import Any, TypeVar, Callable, Awaitable +from typing import TypeVar, Callable, Awaitable from typing_extensions import ParamSpec import anyio @@ -15,34 +13,11 @@ T_ParamSpec = ParamSpec("T_ParamSpec") -if sys.version_info >= (3, 9): - _asyncio_to_thread = asyncio.to_thread -else: - # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread - # for Python 3.8 support - async def _asyncio_to_thread( - func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs - ) -> Any: - """Asynchronously run function *func* in a separate thread. - - Any *args and **kwargs supplied for this function are directly passed - to *func*. Also, the current :class:`contextvars.Context` is propagated, - allowing context variables from the main thread to be accessed in the - separate thread. - - Returns a coroutine that can be awaited to get the eventual result of *func*. - """ - loop = asyncio.events.get_running_loop() - ctx = contextvars.copy_context() - func_call = functools.partial(ctx.run, func, *args, **kwargs) - return await loop.run_in_executor(None, func_call) - - async def to_thread( func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs ) -> T_Retval: if sniffio.current_async_library() == "asyncio": - return await _asyncio_to_thread(func, *args, **kwargs) + return await asyncio.to_thread(func, *args, **kwargs) return await anyio.to_thread.run_sync( functools.partial(func, *args, **kwargs), @@ -53,10 +28,7 @@ async def to_thread( def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments. For python version 3.9 and above, it uses - asyncio.to_thread to run the function in a separate thread. For python version - 3.8, it uses locally defined copy of the asyncio.to_thread function which was - introduced in python 3.9. + positional and keyword arguments. Usage: From 8ec89c3b981d13dac5d4ccd383c780919fb2fc4f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 03:32:58 +0000 Subject: [PATCH 292/320] fix: compat with Python 3.14 --- src/codex/_models.py | 11 ++++++++--- tests/test_models.py | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index 6a3cd1d2..fcec2cf9 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -2,6 +2,7 @@ import os import inspect +import weakref from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( @@ -573,6 +574,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. @@ -615,8 +619,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 @@ -669,7 +674,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, discriminator_field=discriminator_field_name, discriminator_alias=discriminator_alias, ) - cast(CachedDiscriminatorType, union).__discriminator__ = details + DISCRIMINATOR_CACHE.setdefault(union, details) return details diff --git a/tests/test_models.py b/tests/test_models.py index 61fe5946..b4f6c31a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -9,7 +9,7 @@ from codex._utils import PropertyInfo from codex._compat import PYDANTIC_V1, parse_obj, model_dump, model_json -from codex._models import BaseModel, construct_type +from codex._models import DISCRIMINATOR_CACHE, BaseModel, construct_type class BasicModel(BaseModel): @@ -809,7 +809,7 @@ class B(BaseModel): UnionType = cast(Any, Union[A, B]) - assert not hasattr(UnionType, "__discriminator__") + assert not DISCRIMINATOR_CACHE.get(UnionType) m = construct_type( value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) @@ -818,7 +818,7 @@ class B(BaseModel): assert m.type == "b" assert m.data == "foo" # type: ignore[comparison-overlap] - discriminator = UnionType.__discriminator__ + discriminator = DISCRIMINATOR_CACHE.get(UnionType) assert discriminator is not None m = construct_type( @@ -830,7 +830,7 @@ 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") From 34ae6ce240e9c361c8f244215f416651be7ed3cb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 03:26:52 +0000 Subject: [PATCH 293/320] fix(compat): update signatures of `model_dump` and `model_dump_json` for Pydantic v1 --- src/codex/_models.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index fcec2cf9..ca9500b2 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -257,15 +257,16 @@ def model_dump( mode: Literal["json", "python"] | str = "python", 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, - serialize_as_any: bool = False, 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 @@ -273,16 +274,24 @@ def model_dump( Args: mode: The mode in which `to_python` should run. - If mode is 'json', the dictionary will only contain JSON serializable types. - If mode is 'python', the dictionary may contain any Python objects. - include: A list of fields to include in the output. - exclude: A list of fields to exclude from the output. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. by_alias: Whether to use the field's alias in the dictionary key if defined. - exclude_unset: Whether to exclude fields that are unset or None from the output. - exclude_defaults: Whether to exclude fields that are set to their default value from the output. - exclude_none: Whether to exclude fields that have a value of `None` from the output. - round_trip: Whether to enable serialization and deserialization round-trip support. - warnings: Whether to log warnings when invalid fields are encountered. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. Returns: A dictionary representation of the model. @@ -299,6 +308,8 @@ def model_dump( raise ValueError("serialize_as_any is only supported in Pydantic v2") if fallback is not None: raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, @@ -315,15 +326,17 @@ def model_dump_json( self, *, indent: int | None = None, + 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: @@ -355,6 +368,10 @@ def model_dump_json( 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, From 70d352368c0ca1d81d66bd75d252b95d6b1b1f60 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:17:53 +0000 Subject: [PATCH 294/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index a8db3516..f3d12103 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 0f9ec74cbe1e9972d20ea57aded46fb9 +openapi_spec_hash: 38feba6a7ee93c0f219c7312098b38ce config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 From 3a1908259b58ed00b717faed5b08f19869c3bdcd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:17:58 +0000 Subject: [PATCH 295/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f3d12103..9f71dbb9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: 38feba6a7ee93c0f219c7312098b38ce +openapi_spec_hash: a375d23d90f1d78abd9811ccf7f52bea config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 From e52032b38798984e0712102b291f28b12f1ad026 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 06:17:42 +0000 Subject: [PATCH 296/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 79 +- src/codex/resources/projects/remediations.py | 307 ++-- .../query_log_list_by_group_response.py | 9 +- .../query_log_list_groups_response.py | 9 +- .../types/projects/query_log_list_response.py | 9 +- .../projects/query_log_retrieve_response.py | 16 +- ...remediation_list_resolved_logs_response.py | 9 +- .../api_resources/projects/test_query_logs.py | 450 +++--- .../projects/test_remediations.py | 1328 +++++++++-------- 10 files changed, 1252 insertions(+), 966 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9f71dbb9..2e45ba59 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 56 -openapi_spec_hash: a375d23d90f1d78abd9811ccf7f52bea +openapi_spec_hash: d273ca5158facc1251efa0a5f9e723c5 config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index 2720dd15..fec11389 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import List, Union, Optional from datetime import datetime from typing_extensions import Literal @@ -252,6 +253,7 @@ def add_user_feedback( cast_to=QueryLogAddUserFeedbackResponse, ) + @typing_extensions.deprecated("deprecated") def list_by_group( self, project_id: str, @@ -372,6 +374,7 @@ def list_by_group( cast_to=QueryLogListByGroupResponse, ) + @typing_extensions.deprecated("deprecated") def list_groups( self, project_id: str, @@ -490,6 +493,7 @@ def list_groups( model=QueryLogListGroupsResponse, ) + @typing_extensions.deprecated("deprecated") def start_remediation( self, query_log_id: str, @@ -776,6 +780,7 @@ async def add_user_feedback( cast_to=QueryLogAddUserFeedbackResponse, ) + @typing_extensions.deprecated("deprecated") async def list_by_group( self, project_id: str, @@ -896,6 +901,7 @@ async def list_by_group( cast_to=QueryLogListByGroupResponse, ) + @typing_extensions.deprecated("deprecated") def list_groups( self, project_id: str, @@ -1014,6 +1020,7 @@ def list_groups( model=QueryLogListGroupsResponse, ) + @typing_extensions.deprecated("deprecated") async def start_remediation( self, query_log_id: str, @@ -1102,14 +1109,20 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.add_user_feedback = to_raw_response_wrapper( query_logs.add_user_feedback, ) - self.list_by_group = to_raw_response_wrapper( - query_logs.list_by_group, + self.list_by_group = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + query_logs.list_by_group, # pyright: ignore[reportDeprecated], + ) ) - self.list_groups = to_raw_response_wrapper( - query_logs.list_groups, + self.list_groups = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + query_logs.list_groups, # pyright: ignore[reportDeprecated], + ) ) - self.start_remediation = to_raw_response_wrapper( - query_logs.start_remediation, + self.start_remediation = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + query_logs.start_remediation, # pyright: ignore[reportDeprecated], + ) ) self.update_metadata = to_raw_response_wrapper( query_logs.update_metadata, @@ -1129,14 +1142,20 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.add_user_feedback = async_to_raw_response_wrapper( query_logs.add_user_feedback, ) - self.list_by_group = async_to_raw_response_wrapper( - query_logs.list_by_group, + self.list_by_group = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + query_logs.list_by_group, # pyright: ignore[reportDeprecated], + ) ) - self.list_groups = async_to_raw_response_wrapper( - query_logs.list_groups, + self.list_groups = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + query_logs.list_groups, # pyright: ignore[reportDeprecated], + ) ) - self.start_remediation = async_to_raw_response_wrapper( - query_logs.start_remediation, + self.start_remediation = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + query_logs.start_remediation, # pyright: ignore[reportDeprecated], + ) ) self.update_metadata = async_to_raw_response_wrapper( query_logs.update_metadata, @@ -1156,14 +1175,20 @@ def __init__(self, query_logs: QueryLogsResource) -> None: self.add_user_feedback = to_streamed_response_wrapper( query_logs.add_user_feedback, ) - self.list_by_group = to_streamed_response_wrapper( - query_logs.list_by_group, + self.list_by_group = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + query_logs.list_by_group, # pyright: ignore[reportDeprecated], + ) ) - self.list_groups = to_streamed_response_wrapper( - query_logs.list_groups, + self.list_groups = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + query_logs.list_groups, # pyright: ignore[reportDeprecated], + ) ) - self.start_remediation = to_streamed_response_wrapper( - query_logs.start_remediation, + self.start_remediation = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + query_logs.start_remediation, # pyright: ignore[reportDeprecated], + ) ) self.update_metadata = to_streamed_response_wrapper( query_logs.update_metadata, @@ -1183,14 +1208,20 @@ def __init__(self, query_logs: AsyncQueryLogsResource) -> None: self.add_user_feedback = async_to_streamed_response_wrapper( query_logs.add_user_feedback, ) - self.list_by_group = async_to_streamed_response_wrapper( - query_logs.list_by_group, + self.list_by_group = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + query_logs.list_by_group, # pyright: ignore[reportDeprecated], + ) ) - self.list_groups = async_to_streamed_response_wrapper( - query_logs.list_groups, + self.list_groups = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + query_logs.list_groups, # pyright: ignore[reportDeprecated], + ) ) - self.start_remediation = async_to_streamed_response_wrapper( - query_logs.start_remediation, + self.start_remediation = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + query_logs.start_remediation, # pyright: ignore[reportDeprecated], + ) ) self.update_metadata = async_to_streamed_response_wrapper( query_logs.update_metadata, diff --git a/src/codex/resources/projects/remediations.py b/src/codex/resources/projects/remediations.py index 11642624..8fc26f0b 100644 --- a/src/codex/resources/projects/remediations.py +++ b/src/codex/resources/projects/remediations.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import List, Union, Optional from datetime import datetime from typing_extensions import Literal @@ -60,6 +61,7 @@ def with_streaming_response(self) -> RemediationsResourceWithStreamingResponse: """ return RemediationsResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") def create( self, project_id: str, @@ -104,6 +106,7 @@ def create( cast_to=RemediationCreateResponse, ) + @typing_extensions.deprecated("deprecated") def retrieve( self, remediation_id: str, @@ -140,6 +143,7 @@ def retrieve( cast_to=RemediationRetrieveResponse, ) + @typing_extensions.deprecated("deprecated") def list( self, project_id: str, @@ -214,6 +218,7 @@ def list( model=RemediationListResponse, ) + @typing_extensions.deprecated("deprecated") def delete( self, remediation_id: str, @@ -251,6 +256,7 @@ def delete( cast_to=NoneType, ) + @typing_extensions.deprecated("deprecated") def edit_answer( self, remediation_id: str, @@ -289,6 +295,7 @@ def edit_answer( cast_to=RemediationEditAnswerResponse, ) + @typing_extensions.deprecated("deprecated") def edit_draft_answer( self, remediation_id: str, @@ -329,6 +336,7 @@ def edit_draft_answer( cast_to=RemediationEditDraftAnswerResponse, ) + @typing_extensions.deprecated("deprecated") def get_resolved_logs_count( self, remediation_id: str, @@ -365,6 +373,7 @@ def get_resolved_logs_count( cast_to=RemediationGetResolvedLogsCountResponse, ) + @typing_extensions.deprecated("deprecated") def list_resolved_logs( self, remediation_id: str, @@ -401,6 +410,7 @@ def list_resolved_logs( cast_to=RemediationListResolvedLogsResponse, ) + @typing_extensions.deprecated("deprecated") def pause( self, remediation_id: str, @@ -437,6 +447,7 @@ def pause( cast_to=RemediationPauseResponse, ) + @typing_extensions.deprecated("deprecated") def publish( self, remediation_id: str, @@ -473,6 +484,7 @@ def publish( cast_to=RemediationPublishResponse, ) + @typing_extensions.deprecated("deprecated") def unpause( self, remediation_id: str, @@ -530,6 +542,7 @@ def with_streaming_response(self) -> AsyncRemediationsResourceWithStreamingRespo """ return AsyncRemediationsResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") async def create( self, project_id: str, @@ -574,6 +587,7 @@ async def create( cast_to=RemediationCreateResponse, ) + @typing_extensions.deprecated("deprecated") async def retrieve( self, remediation_id: str, @@ -610,6 +624,7 @@ async def retrieve( cast_to=RemediationRetrieveResponse, ) + @typing_extensions.deprecated("deprecated") def list( self, project_id: str, @@ -684,6 +699,7 @@ def list( model=RemediationListResponse, ) + @typing_extensions.deprecated("deprecated") async def delete( self, remediation_id: str, @@ -721,6 +737,7 @@ async def delete( cast_to=NoneType, ) + @typing_extensions.deprecated("deprecated") async def edit_answer( self, remediation_id: str, @@ -761,6 +778,7 @@ async def edit_answer( cast_to=RemediationEditAnswerResponse, ) + @typing_extensions.deprecated("deprecated") async def edit_draft_answer( self, remediation_id: str, @@ -801,6 +819,7 @@ async def edit_draft_answer( cast_to=RemediationEditDraftAnswerResponse, ) + @typing_extensions.deprecated("deprecated") async def get_resolved_logs_count( self, remediation_id: str, @@ -837,6 +856,7 @@ async def get_resolved_logs_count( cast_to=RemediationGetResolvedLogsCountResponse, ) + @typing_extensions.deprecated("deprecated") async def list_resolved_logs( self, remediation_id: str, @@ -873,6 +893,7 @@ async def list_resolved_logs( cast_to=RemediationListResolvedLogsResponse, ) + @typing_extensions.deprecated("deprecated") async def pause( self, remediation_id: str, @@ -909,6 +930,7 @@ async def pause( cast_to=RemediationPauseResponse, ) + @typing_extensions.deprecated("deprecated") async def publish( self, remediation_id: str, @@ -945,6 +967,7 @@ async def publish( cast_to=RemediationPublishResponse, ) + @typing_extensions.deprecated("deprecated") async def unpause( self, remediation_id: str, @@ -986,38 +1009,60 @@ class RemediationsResourceWithRawResponse: def __init__(self, remediations: RemediationsResource) -> None: self._remediations = remediations - self.create = to_raw_response_wrapper( - remediations.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_raw_response_wrapper( - remediations.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_raw_response_wrapper( - remediations.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_raw_response_wrapper( - remediations.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.delete, # pyright: ignore[reportDeprecated], + ) ) - self.edit_answer = to_raw_response_wrapper( - remediations.edit_answer, + self.edit_answer = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.edit_answer, # pyright: ignore[reportDeprecated], + ) ) - self.edit_draft_answer = to_raw_response_wrapper( - remediations.edit_draft_answer, + self.edit_draft_answer = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.edit_draft_answer, # pyright: ignore[reportDeprecated], + ) ) - self.get_resolved_logs_count = to_raw_response_wrapper( - remediations.get_resolved_logs_count, + self.get_resolved_logs_count = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.get_resolved_logs_count, # pyright: ignore[reportDeprecated], + ) ) - self.list_resolved_logs = to_raw_response_wrapper( - remediations.list_resolved_logs, + self.list_resolved_logs = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.list_resolved_logs, # pyright: ignore[reportDeprecated], + ) ) - self.pause = to_raw_response_wrapper( - remediations.pause, + self.pause = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.pause, # pyright: ignore[reportDeprecated], + ) ) - self.publish = to_raw_response_wrapper( - remediations.publish, + self.publish = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.publish, # pyright: ignore[reportDeprecated], + ) ) - self.unpause = to_raw_response_wrapper( - remediations.unpause, + self.unpause = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + remediations.unpause, # pyright: ignore[reportDeprecated], + ) ) @@ -1025,38 +1070,60 @@ class AsyncRemediationsResourceWithRawResponse: def __init__(self, remediations: AsyncRemediationsResource) -> None: self._remediations = remediations - self.create = async_to_raw_response_wrapper( - remediations.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_raw_response_wrapper( - remediations.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_raw_response_wrapper( - remediations.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_raw_response_wrapper( - remediations.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.delete, # pyright: ignore[reportDeprecated], + ) ) - self.edit_answer = async_to_raw_response_wrapper( - remediations.edit_answer, + self.edit_answer = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.edit_answer, # pyright: ignore[reportDeprecated], + ) ) - self.edit_draft_answer = async_to_raw_response_wrapper( - remediations.edit_draft_answer, + self.edit_draft_answer = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.edit_draft_answer, # pyright: ignore[reportDeprecated], + ) ) - self.get_resolved_logs_count = async_to_raw_response_wrapper( - remediations.get_resolved_logs_count, + self.get_resolved_logs_count = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.get_resolved_logs_count, # pyright: ignore[reportDeprecated], + ) ) - self.list_resolved_logs = async_to_raw_response_wrapper( - remediations.list_resolved_logs, + self.list_resolved_logs = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.list_resolved_logs, # pyright: ignore[reportDeprecated], + ) ) - self.pause = async_to_raw_response_wrapper( - remediations.pause, + self.pause = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.pause, # pyright: ignore[reportDeprecated], + ) ) - self.publish = async_to_raw_response_wrapper( - remediations.publish, + self.publish = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.publish, # pyright: ignore[reportDeprecated], + ) ) - self.unpause = async_to_raw_response_wrapper( - remediations.unpause, + self.unpause = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + remediations.unpause, # pyright: ignore[reportDeprecated], + ) ) @@ -1064,38 +1131,60 @@ class RemediationsResourceWithStreamingResponse: def __init__(self, remediations: RemediationsResource) -> None: self._remediations = remediations - self.create = to_streamed_response_wrapper( - remediations.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_streamed_response_wrapper( - remediations.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_streamed_response_wrapper( - remediations.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_streamed_response_wrapper( - remediations.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.delete, # pyright: ignore[reportDeprecated], + ) ) - self.edit_answer = to_streamed_response_wrapper( - remediations.edit_answer, + self.edit_answer = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.edit_answer, # pyright: ignore[reportDeprecated], + ) ) - self.edit_draft_answer = to_streamed_response_wrapper( - remediations.edit_draft_answer, + self.edit_draft_answer = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.edit_draft_answer, # pyright: ignore[reportDeprecated], + ) ) - self.get_resolved_logs_count = to_streamed_response_wrapper( - remediations.get_resolved_logs_count, + self.get_resolved_logs_count = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.get_resolved_logs_count, # pyright: ignore[reportDeprecated], + ) ) - self.list_resolved_logs = to_streamed_response_wrapper( - remediations.list_resolved_logs, + self.list_resolved_logs = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.list_resolved_logs, # pyright: ignore[reportDeprecated], + ) ) - self.pause = to_streamed_response_wrapper( - remediations.pause, + self.pause = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.pause, # pyright: ignore[reportDeprecated], + ) ) - self.publish = to_streamed_response_wrapper( - remediations.publish, + self.publish = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.publish, # pyright: ignore[reportDeprecated], + ) ) - self.unpause = to_streamed_response_wrapper( - remediations.unpause, + self.unpause = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + remediations.unpause, # pyright: ignore[reportDeprecated], + ) ) @@ -1103,36 +1192,58 @@ class AsyncRemediationsResourceWithStreamingResponse: def __init__(self, remediations: AsyncRemediationsResource) -> None: self._remediations = remediations - self.create = async_to_streamed_response_wrapper( - remediations.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - remediations.retrieve, - ) - self.list = async_to_streamed_response_wrapper( - remediations.list, - ) - self.delete = async_to_streamed_response_wrapper( - remediations.delete, - ) - self.edit_answer = async_to_streamed_response_wrapper( - remediations.edit_answer, - ) - self.edit_draft_answer = async_to_streamed_response_wrapper( - remediations.edit_draft_answer, - ) - self.get_resolved_logs_count = async_to_streamed_response_wrapper( - remediations.get_resolved_logs_count, - ) - self.list_resolved_logs = async_to_streamed_response_wrapper( - remediations.list_resolved_logs, - ) - self.pause = async_to_streamed_response_wrapper( - remediations.pause, - ) - self.publish = async_to_streamed_response_wrapper( - remediations.publish, - ) - self.unpause = async_to_streamed_response_wrapper( - remediations.unpause, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.create, # pyright: ignore[reportDeprecated], + ) + ) + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.retrieve, # pyright: ignore[reportDeprecated], + ) + ) + self.list = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.list, # pyright: ignore[reportDeprecated], + ) + ) + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.delete, # pyright: ignore[reportDeprecated], + ) + ) + self.edit_answer = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.edit_answer, # pyright: ignore[reportDeprecated], + ) + ) + self.edit_draft_answer = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.edit_draft_answer, # pyright: ignore[reportDeprecated], + ) + ) + self.get_resolved_logs_count = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.get_resolved_logs_count, # pyright: ignore[reportDeprecated], + ) + ) + self.list_resolved_logs = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.list_resolved_logs, # pyright: ignore[reportDeprecated], + ) + ) + self.pause = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.pause, # pyright: ignore[reportDeprecated], + ) + ) + self.publish = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.publish, # pyright: ignore[reportDeprecated], + ) + ) + self.unpause = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + remediations.unpause, # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 2877e084..ed1a32d0 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -398,14 +398,14 @@ class QueryLogsByGroupQueryLog(BaseModel): is_bad_response: bool + issue_id: Optional[str] = None + needs_review: bool project_id: str question: str - remediation_id: str - remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] tool_call_names: Optional[List[str]] = None @@ -494,8 +494,6 @@ class QueryLogsByGroupQueryLog(BaseModel): the highest priority """ - issue_id: Optional[str] = None - manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" @@ -525,9 +523,6 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - served_remediation_id: Optional[str] = None - """ID of the remediation that was served if cache hit, otherwise None.""" - tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index be46259b..d20eb8c3 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -391,14 +391,14 @@ class QueryLogListGroupsResponse(BaseModel): is_bad_response: bool + issue_id: Optional[str] = None + needs_review: bool project_id: str question: str - remediation_id: str - remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] tool_call_names: Optional[List[str]] = None @@ -489,8 +489,6 @@ class QueryLogListGroupsResponse(BaseModel): the highest priority """ - issue_id: Optional[str] = None - manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" @@ -520,9 +518,6 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - served_remediation_id: Optional[str] = None - """ID of the remediation that was served if cache hit, otherwise None.""" - tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 7558ae34..9d8d1985 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -385,12 +385,12 @@ class QueryLogListResponse(BaseModel): is_bad_response: bool + issue_id: Optional[str] = None + project_id: str question: str - remediation_id: str - tool_call_names: Optional[List[str]] = None was_cache_hit: Optional[bool] = None @@ -477,8 +477,6 @@ class QueryLogListResponse(BaseModel): the highest priority """ - issue_id: Optional[str] = None - messages: Optional[List[Message]] = None """Message history to provide conversation context for the query. @@ -505,9 +503,6 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - served_remediation_id: Optional[str] = None - """ID of the remediation that was served if cache hit, otherwise None.""" - tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 8851069a..3943325c 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -365,6 +365,8 @@ class QueryLogRetrieveResponse(BaseModel): created_at: datetime + expert_answer_id: Optional[str] = None + formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None @@ -385,16 +387,17 @@ class QueryLogRetrieveResponse(BaseModel): is_bad_response: bool + issue_id: Optional[str] = None + + issue_status: Literal["addressed", "unaddressed"] + """Manual review status override for remediations.""" + needs_review: bool project_id: str question: str - remediation_id: str - - remediation_status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "NOT_STARTED", "PAUSED", "NO_ACTION_NEEDED"] - tool_call_names: Optional[List[str]] = None was_cache_hit: Optional[bool] = None @@ -481,8 +484,6 @@ class QueryLogRetrieveResponse(BaseModel): the highest priority """ - issue_id: Optional[str] = None - manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None """Manual review status override for remediations.""" @@ -512,9 +513,6 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - served_remediation_id: Optional[str] = None - """ID of the remediation that was served if cache hit, otherwise None.""" - tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 5ed3c015..e586b142 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -392,12 +392,12 @@ class QueryLog(BaseModel): is_bad_response: bool + issue_id: Optional[str] = None + project_id: str question: str - remediation_id: str - tool_call_names: Optional[List[str]] = None was_cache_hit: Optional[bool] = None @@ -484,8 +484,6 @@ class QueryLog(BaseModel): the highest priority """ - issue_id: Optional[str] = None - messages: Optional[List[QueryLogMessage]] = None """Message history to provide conversation context for the query. @@ -512,9 +510,6 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" - served_remediation_id: Optional[str] = None - """ID of the remediation that was served if cache hit, otherwise None.""" - tools: Optional[List[QueryLogTool]] = None """Tools to use for the LLM call. diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 51262fe0..e98cb278 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -26,6 +26,8 @@ QueryLogStartRemediationResponse, ) +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -210,43 +212,48 @@ def test_path_params_add_user_feedback(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_by_group(self, client: Codex) -> None: - query_log = client.projects.query_logs.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + query_log = client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_by_group_with_all_params(self, client: Codex) -> None: - query_log = client.projects.query_logs.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - custom_metadata="custom_metadata", - expert_review_status="good", - failed_evals=["string"], - guardrailed=True, - has_tool_calls=True, - limit=1, - needs_review=True, - offset=0, - order="asc", - passed_evals=["string"], - primary_eval_issue=["hallucination"], - remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - search_text="search_text", - sort="created_at", - tool_call_names=["string"], - was_cache_hit=True, - ) + with pytest.warns(DeprecationWarning): + query_log = client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + expert_review_status="good", + failed_evals=["string"], + guardrailed=True, + has_tool_calls=True, + limit=1, + needs_review=True, + offset=0, + order="asc", + passed_evals=["string"], + primary_eval_issue=["hallucination"], + remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + search_text="search_text", + sort="created_at", + tool_call_names=["string"], + was_cache_hit=True, + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list_by_group(self, client: Codex) -> None: - response = client.projects.query_logs.with_raw_response.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.query_logs.with_raw_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -256,64 +263,71 @@ def test_raw_response_list_by_group(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list_by_group(self, client: Codex) -> None: - with client.projects.query_logs.with_streaming_response.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.query_logs.with_streaming_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = response.parse() - assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + query_log = response.parse() + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list_by_group(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.query_logs.with_raw_response.list_by_group( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.list_by_group( + project_id="", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_groups(self, client: Codex) -> None: - query_log = client.projects.query_logs.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + query_log = client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_groups_with_all_params(self, client: Codex) -> None: - query_log = client.projects.query_logs.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - custom_metadata="custom_metadata", - expert_review_status="good", - failed_evals=["string"], - guardrailed=True, - has_tool_calls=True, - limit=1, - needs_review=True, - offset=0, - order="asc", - passed_evals=["string"], - primary_eval_issue=["hallucination"], - search_text="search_text", - sort="created_at", - tool_call_names=["string"], - was_cache_hit=True, - ) + with pytest.warns(DeprecationWarning): + query_log = client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + expert_review_status="good", + failed_evals=["string"], + guardrailed=True, + has_tool_calls=True, + limit=1, + needs_review=True, + offset=0, + order="asc", + passed_evals=["string"], + primary_eval_issue=["hallucination"], + search_text="search_text", + sort="created_at", + tool_call_names=["string"], + was_cache_hit=True, + ) + assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list_groups(self, client: Codex) -> None: - response = client.projects.query_logs.with_raw_response.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.query_logs.with_raw_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -323,41 +337,48 @@ def test_raw_response_list_groups(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list_groups(self, client: Codex) -> None: - with client.projects.query_logs.with_streaming_response.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.query_logs.with_streaming_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = response.parse() - assert_matches_type(SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) + query_log = response.parse() + assert_matches_type( + SyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"] + ) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list_groups(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.query_logs.with_raw_response.list_groups( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.list_groups( + project_id="", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_start_remediation(self, client: Codex) -> None: - query_log = client.projects.query_logs.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + query_log = client.projects.query_logs.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_start_remediation(self, client: Codex) -> None: - response = client.projects.query_logs.with_raw_response.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -367,32 +388,34 @@ def test_raw_response_start_remediation(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_start_remediation(self, client: Codex) -> None: - with client.projects.query_logs.with_streaming_response.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.query_logs.with_streaming_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = response.parse() - assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + query_log = response.parse() + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_start_remediation(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.query_logs.with_raw_response.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): - client.projects.query_logs.with_raw_response.start_remediation( - query_log_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -635,43 +658,48 @@ async def test_path_params_add_user_feedback(self, async_client: AsyncCodex) -> @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_by_group(self, async_client: AsyncCodex) -> None: - query_log = await async_client.projects.query_logs.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + query_log = await async_client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_by_group_with_all_params(self, async_client: AsyncCodex) -> None: - query_log = await async_client.projects.query_logs.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - custom_metadata="custom_metadata", - expert_review_status="good", - failed_evals=["string"], - guardrailed=True, - has_tool_calls=True, - limit=1, - needs_review=True, - offset=0, - order="asc", - passed_evals=["string"], - primary_eval_issue=["hallucination"], - remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], - search_text="search_text", - sort="created_at", - tool_call_names=["string"], - was_cache_hit=True, - ) + with pytest.warns(DeprecationWarning): + query_log = await async_client.projects.query_logs.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + expert_review_status="good", + failed_evals=["string"], + guardrailed=True, + has_tool_calls=True, + limit=1, + needs_review=True, + offset=0, + order="asc", + passed_evals=["string"], + primary_eval_issue=["hallucination"], + remediation_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + search_text="search_text", + sort="created_at", + tool_call_names=["string"], + was_cache_hit=True, + ) + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.query_logs.with_raw_response.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.query_logs.with_raw_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -681,64 +709,71 @@ async def test_raw_response_list_by_group(self, async_client: AsyncCodex) -> Non @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list_by_group(self, async_client: AsyncCodex) -> None: - async with async_client.projects.query_logs.with_streaming_response.list_by_group( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.query_logs.with_streaming_response.list_by_group( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = await response.parse() - assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) + query_log = await response.parse() + assert_matches_type(QueryLogListByGroupResponse, query_log, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list_by_group(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.query_logs.with_raw_response.list_by_group( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.list_by_group( + project_id="", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_groups(self, async_client: AsyncCodex) -> None: - query_log = await async_client.projects.query_logs.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + query_log = await async_client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex) -> None: - query_log = await async_client.projects.query_logs.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - custom_metadata="custom_metadata", - expert_review_status="good", - failed_evals=["string"], - guardrailed=True, - has_tool_calls=True, - limit=1, - needs_review=True, - offset=0, - order="asc", - passed_evals=["string"], - primary_eval_issue=["hallucination"], - search_text="search_text", - sort="created_at", - tool_call_names=["string"], - was_cache_hit=True, - ) + with pytest.warns(DeprecationWarning): + query_log = await async_client.projects.query_logs.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + custom_metadata="custom_metadata", + expert_review_status="good", + failed_evals=["string"], + guardrailed=True, + has_tool_calls=True, + limit=1, + needs_review=True, + offset=0, + order="asc", + passed_evals=["string"], + primary_eval_issue=["hallucination"], + search_text="search_text", + sort="created_at", + tool_call_names=["string"], + was_cache_hit=True, + ) + assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.query_logs.with_raw_response.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.query_logs.with_raw_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -748,41 +783,48 @@ async def test_raw_response_list_groups(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list_groups(self, async_client: AsyncCodex) -> None: - async with async_client.projects.query_logs.with_streaming_response.list_groups( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.query_logs.with_streaming_response.list_groups( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = await response.parse() - assert_matches_type(AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"]) + query_log = await response.parse() + assert_matches_type( + AsyncOffsetPageQueryLogGroups[QueryLogListGroupsResponse], query_log, path=["response"] + ) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list_groups(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.query_logs.with_raw_response.list_groups( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.list_groups( + project_id="", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_start_remediation(self, async_client: AsyncCodex) -> None: - query_log = await async_client.projects.query_logs.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + query_log = await async_client.projects.query_logs.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_start_remediation(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.query_logs.with_raw_response.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -792,32 +834,34 @@ async def test_raw_response_start_remediation(self, async_client: AsyncCodex) -> @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_start_remediation(self, async_client: AsyncCodex) -> None: - async with async_client.projects.query_logs.with_streaming_response.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.query_logs.with_streaming_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query_log = await response.parse() - assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) + query_log = await response.parse() + assert_matches_type(QueryLogStartRemediationResponse, query_log, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_start_remediation(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.query_logs.with_raw_response.start_remediation( - query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): - await async_client.projects.query_logs.with_raw_response.start_remediation( - query_log_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `query_log_id` but received ''"): + await async_client.projects.query_logs.with_raw_response.start_remediation( + query_log_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize diff --git a/tests/api_resources/projects/test_remediations.py b/tests/api_resources/projects/test_remediations.py index d547ac85..37bb6556 100644 --- a/tests/api_resources/projects/test_remediations.py +++ b/tests/api_resources/projects/test_remediations.py @@ -24,6 +24,8 @@ RemediationGetResolvedLogsCountResponse, ) +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -33,30 +35,35 @@ class TestRemediations: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Codex) -> None: - remediation = client.projects.remediations.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Codex) -> None: - remediation = client.projects.remediations.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - answer="answer", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + answer="answer", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -66,43 +73,48 @@ def test_raw_response_create(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_create(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.create( - project_id="", - question="x", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.create( + project_id="", + question="x", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Codex) -> None: - remediation = client.projects.remediations.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -112,65 +124,72 @@ def test_raw_response_retrieve(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.retrieve( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.retrieve( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Codex) -> None: - remediation = client.projects.remediations.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: - remediation = client.projects.remediations.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - last_edited_by="last_edited_by", - limit=1, - offset=0, - order="asc", - sort="created_at", - status=["ACTIVE"], - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + sort="created_at", + status=["ACTIVE"], + ) + assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -180,41 +199,46 @@ def test_raw_response_list(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(SyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.list( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.list( + project_id="", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_delete(self, client: Codex) -> None: - remediation = client.projects.remediations.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert remediation is None @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_delete(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -224,51 +248,56 @@ def test_raw_response_delete(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_delete(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert remediation is None + remediation = response.parse() + assert remediation is None assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_delete(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.delete( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.delete( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_edit_answer(self, client: Codex) -> None: - remediation = client.projects.remediations.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_edit_answer(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -278,54 +307,59 @@ def test_raw_response_edit_answer(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_edit_answer(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_edit_answer(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - answer="answer", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + answer="answer", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.edit_answer( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.edit_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_edit_draft_answer(self, client: Codex) -> None: - remediation = client.projects.remediations.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_edit_draft_answer(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -335,52 +369,57 @@ def test_raw_response_edit_draft_answer(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_edit_draft_answer(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_edit_draft_answer(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + draft_answer="draft_answer", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.edit_draft_answer( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_get_resolved_logs_count(self, client: Codex) -> None: - remediation = client.projects.remediations.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -390,49 +429,54 @@ def test_raw_response_get_resolved_logs_count(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_get_resolved_logs_count(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_get_resolved_logs_count(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.get_resolved_logs_count( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_resolved_logs(self, client: Codex) -> None: - remediation = client.projects.remediations.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list_resolved_logs(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -442,49 +486,54 @@ def test_raw_response_list_resolved_logs(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list_resolved_logs(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_list_resolved_logs(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.list_resolved_logs( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_pause(self, client: Codex) -> None: - remediation = client.projects.remediations.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_pause(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -494,49 +543,54 @@ def test_raw_response_pause(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_pause(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_pause(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.pause( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.pause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_publish(self, client: Codex) -> None: - remediation = client.projects.remediations.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_publish(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -546,49 +600,54 @@ def test_raw_response_publish(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_publish(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_publish(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.publish( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.publish( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_unpause(self, client: Codex) -> None: - remediation = client.projects.remediations.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = client.projects.remediations.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_unpause(self, client: Codex) -> None: - response = client.projects.remediations.with_raw_response.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -598,32 +657,34 @@ def test_raw_response_unpause(self, client: Codex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_unpause(self, client: Codex) -> None: - with client.projects.remediations.with_streaming_response.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.projects.remediations.with_streaming_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = response.parse() - assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + remediation = response.parse() + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_unpause(self, client: Codex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - client.projects.remediations.with_raw_response.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - client.projects.remediations.with_raw_response.unpause( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + client.projects.remediations.with_raw_response.unpause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) class TestAsyncRemediations: @@ -634,30 +695,35 @@ class TestAsyncRemediations: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - answer="answer", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + answer="answer", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -667,43 +733,48 @@ async def test_raw_response_create(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.create( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - question="x", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + question="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationCreateResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_create(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.create( - project_id="", - question="x", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.create( + project_id="", + question="x", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -713,65 +784,72 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationRetrieveResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.retrieve( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.retrieve( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.retrieve( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.retrieve( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), - last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), - last_edited_by="last_edited_by", - limit=1, - offset=0, - order="asc", - sort="created_at", - status=["ACTIVE"], - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + sort="created_at", + status=["ACTIVE"], + ) + assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -781,41 +859,48 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.list( - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type( + AsyncOffsetPageRemediations[RemediationListResponse], remediation, path=["response"] + ) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.list( - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.list( + project_id="", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_delete(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert remediation is None @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -825,51 +910,56 @@ async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert remediation is None + remediation = await response.parse() + assert remediation is None assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_delete(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.delete( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.delete( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.delete( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.delete( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_edit_answer(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -879,54 +969,59 @@ async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationEditAnswerResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.edit_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - answer="answer", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + answer="answer", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.edit_answer( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - answer="answer", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -936,52 +1031,57 @@ async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationEditDraftAnswerResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.edit_draft_answer( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - draft_answer="draft_answer", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + draft_answer="draft_answer", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.edit_draft_answer( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - draft_answer="draft_answer", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.edit_draft_answer( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -991,49 +1091,54 @@ async def test_raw_response_get_resolved_logs_count(self, async_client: AsyncCod @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationGetResolvedLogsCountResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_get_resolved_logs_count(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.get_resolved_logs_count( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_resolved_logs(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1043,49 +1148,54 @@ async def test_raw_response_list_resolved_logs(self, async_client: AsyncCodex) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list_resolved_logs(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationListResolvedLogsResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_list_resolved_logs(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.list_resolved_logs( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.list_resolved_logs( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.list_resolved_logs( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_pause(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_pause(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1095,49 +1205,54 @@ async def test_raw_response_pause(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationPauseResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_pause(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.pause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.pause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.pause( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.pause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_publish(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_publish(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1147,49 +1262,54 @@ async def test_raw_response_publish(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_publish(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationPublishResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_publish(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.publish( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.publish( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.publish( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.publish( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_unpause(self, async_client: AsyncCodex) -> None: - remediation = await async_client.projects.remediations.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + remediation = await async_client.projects.remediations.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.remediations.with_raw_response.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1199,29 +1319,31 @@ async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> None: - async with async_client.projects.remediations.with_streaming_response.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.projects.remediations.with_streaming_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - remediation = await response.parse() - assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) + remediation = await response.parse() + assert_matches_type(RemediationUnpauseResponse, remediation, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_unpause(self, async_client: AsyncCodex) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): - await async_client.projects.remediations.with_raw_response.unpause( - remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - project_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): - await async_client.projects.remediations.with_raw_response.unpause( - remediation_id="", - project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.with_raw_response.unpause( + remediation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `remediation_id` but received ''"): + await async_client.projects.remediations.with_raw_response.unpause( + remediation_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) From d4d2ac4046c855b5016ff5e21006f4ca5faade62 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:27:21 +0000 Subject: [PATCH 297/320] feat(api): add expert answer endpoints --- .stats.yml | 4 +- api.md | 51 +- src/codex/pagination.py | 62 + src/codex/resources/projects/projects.py | 4 +- .../projects/remediations/__init__.py | 33 + .../projects/remediations/expert_answers.py | 968 ++++++++++++++++ .../{ => remediations}/remediations.py | 68 +- .../types/projects/remediations/__init__.py | 20 + .../expert_answer_create_params.py | 16 + .../expert_answer_create_response.py | 40 + .../expert_answer_edit_answer_params.py | 13 + .../expert_answer_edit_answer_response.py | 40 + .../expert_answer_edit_draft_answer_params.py | 13 + ...xpert_answer_edit_draft_answer_response.py | 40 + .../remediations/expert_answer_list_params.py | 39 + .../expert_answer_list_response.py | 42 + .../expert_answer_pause_response.py | 40 + .../expert_answer_publish_response.py | 40 + .../expert_answer_retrieve_response.py | 40 + .../expert_answer_unpause_response.py | 40 + .../projects/remediations/__init__.py | 1 + .../remediations/test_expert_answers.py | 1019 +++++++++++++++++ 22 files changed, 2600 insertions(+), 33 deletions(-) create mode 100644 src/codex/resources/projects/remediations/__init__.py create mode 100644 src/codex/resources/projects/remediations/expert_answers.py rename src/codex/resources/projects/{ => remediations}/remediations.py (95%) create mode 100644 src/codex/types/projects/remediations/__init__.py create mode 100644 src/codex/types/projects/remediations/expert_answer_create_params.py create mode 100644 src/codex/types/projects/remediations/expert_answer_create_response.py create mode 100644 src/codex/types/projects/remediations/expert_answer_edit_answer_params.py create mode 100644 src/codex/types/projects/remediations/expert_answer_edit_answer_response.py create mode 100644 src/codex/types/projects/remediations/expert_answer_edit_draft_answer_params.py create mode 100644 src/codex/types/projects/remediations/expert_answer_edit_draft_answer_response.py create mode 100644 src/codex/types/projects/remediations/expert_answer_list_params.py create mode 100644 src/codex/types/projects/remediations/expert_answer_list_response.py create mode 100644 src/codex/types/projects/remediations/expert_answer_pause_response.py create mode 100644 src/codex/types/projects/remediations/expert_answer_publish_response.py create mode 100644 src/codex/types/projects/remediations/expert_answer_retrieve_response.py create mode 100644 src/codex/types/projects/remediations/expert_answer_unpause_response.py create mode 100644 tests/api_resources/projects/remediations/__init__.py create mode 100644 tests/api_resources/projects/remediations/test_expert_answers.py diff --git a/.stats.yml b/.stats.yml index 2e45ba59..49a4e3c8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 56 +configured_endpoints: 65 openapi_spec_hash: d273ca5158facc1251efa0a5f9e723c5 -config_hash: 9e0ed146f9f6e6d1884a4c0589d6f1c2 +config_hash: cd9208a2204f43e0aa5ab35ac85ef90d diff --git a/api.md b/api.md index a2888743..6e8c0535 100644 --- a/api.md +++ b/api.md @@ -244,14 +244,43 @@ from codex.types.projects import ( Methods: -- client.projects.remediations.create(project_id, \*\*params) -> RemediationCreateResponse -- client.projects.remediations.retrieve(remediation_id, \*, project_id) -> RemediationRetrieveResponse -- client.projects.remediations.list(project_id, \*\*params) -> SyncOffsetPageRemediations[RemediationListResponse] -- client.projects.remediations.delete(remediation_id, \*, project_id) -> None -- client.projects.remediations.edit_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditAnswerResponse -- client.projects.remediations.edit_draft_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditDraftAnswerResponse -- client.projects.remediations.get_resolved_logs_count(remediation_id, \*, project_id) -> RemediationGetResolvedLogsCountResponse -- client.projects.remediations.list_resolved_logs(remediation_id, \*, project_id) -> RemediationListResolvedLogsResponse -- client.projects.remediations.pause(remediation_id, \*, project_id) -> RemediationPauseResponse -- client.projects.remediations.publish(remediation_id, \*, project_id) -> RemediationPublishResponse -- client.projects.remediations.unpause(remediation_id, \*, project_id) -> RemediationUnpauseResponse +- client.projects.remediations.create(project_id, \*\*params) -> RemediationCreateResponse +- client.projects.remediations.retrieve(remediation_id, \*, project_id) -> RemediationRetrieveResponse +- client.projects.remediations.list(project_id, \*\*params) -> SyncOffsetPageRemediations[RemediationListResponse] +- client.projects.remediations.delete(remediation_id, \*, project_id) -> None +- client.projects.remediations.edit_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditAnswerResponse +- client.projects.remediations.edit_draft_answer(remediation_id, \*, project_id, \*\*params) -> RemediationEditDraftAnswerResponse +- client.projects.remediations.get_resolved_logs_count(remediation_id, \*, project_id) -> RemediationGetResolvedLogsCountResponse +- client.projects.remediations.list_resolved_logs(remediation_id, \*, project_id) -> RemediationListResolvedLogsResponse +- client.projects.remediations.pause(remediation_id, \*, project_id) -> RemediationPauseResponse +- client.projects.remediations.publish(remediation_id, \*, project_id) -> RemediationPublishResponse +- client.projects.remediations.unpause(remediation_id, \*, project_id) -> RemediationUnpauseResponse + +### ExpertAnswers + +Types: + +```python +from codex.types.projects.remediations import ( + ExpertAnswerCreateResponse, + ExpertAnswerRetrieveResponse, + ExpertAnswerListResponse, + ExpertAnswerEditAnswerResponse, + ExpertAnswerEditDraftAnswerResponse, + ExpertAnswerPauseResponse, + ExpertAnswerPublishResponse, + ExpertAnswerUnpauseResponse, +) +``` + +Methods: + +- client.projects.remediations.expert_answers.create(project_id, \*\*params) -> ExpertAnswerCreateResponse +- client.projects.remediations.expert_answers.retrieve(expert_answer_id, \*, project_id) -> ExpertAnswerRetrieveResponse +- client.projects.remediations.expert_answers.list(project_id, \*\*params) -> SyncOffsetPageExpertAnswers[ExpertAnswerListResponse] +- client.projects.remediations.expert_answers.delete(expert_answer_id, \*, project_id) -> None +- client.projects.remediations.expert_answers.edit_answer(expert_answer_id, \*, project_id, \*\*params) -> ExpertAnswerEditAnswerResponse +- client.projects.remediations.expert_answers.edit_draft_answer(expert_answer_id, \*, project_id, \*\*params) -> ExpertAnswerEditDraftAnswerResponse +- client.projects.remediations.expert_answers.pause(expert_answer_id, \*, project_id) -> ExpertAnswerPauseResponse +- client.projects.remediations.expert_answers.publish(expert_answer_id, \*, project_id) -> ExpertAnswerPublishResponse +- client.projects.remediations.expert_answers.unpause(expert_answer_id, \*, project_id) -> ExpertAnswerUnpauseResponse diff --git a/src/codex/pagination.py b/src/codex/pagination.py index 36af0f63..e5721eb9 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -22,6 +22,8 @@ "AsyncOffsetPageQueryLogGroups", "SyncOffsetPageQueryLogsByGroup", "AsyncOffsetPageQueryLogsByGroup", + "SyncOffsetPageExpertAnswers", + "AsyncOffsetPageExpertAnswers", ] _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) @@ -389,3 +391,63 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) return None + + +class SyncOffsetPageExpertAnswers(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + expert_answers: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + expert_answers = self.expert_answers + if not expert_answers: + return [] + return expert_answers + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncOffsetPageExpertAnswers(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + expert_answers: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + expert_answers = self.expert_answers + if not expert_answers: + return [] + return expert_answers + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 8aa1da79..0152bac5 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -51,7 +51,8 @@ AccessKeysResourceWithStreamingResponse, AsyncAccessKeysResourceWithStreamingResponse, ) -from .remediations import ( +from ..._base_client import make_request_options +from .remediations.remediations import ( RemediationsResource, AsyncRemediationsResource, RemediationsResourceWithRawResponse, @@ -59,7 +60,6 @@ RemediationsResourceWithStreamingResponse, AsyncRemediationsResourceWithStreamingResponse, ) -from ..._base_client import make_request_options from ...types.project_list_response import ProjectListResponse from ...types.project_return_schema import ProjectReturnSchema from ...types.project_detect_response import ProjectDetectResponse diff --git a/src/codex/resources/projects/remediations/__init__.py b/src/codex/resources/projects/remediations/__init__.py new file mode 100644 index 00000000..3a7c4f9e --- /dev/null +++ b/src/codex/resources/projects/remediations/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .remediations import ( + RemediationsResource, + AsyncRemediationsResource, + RemediationsResourceWithRawResponse, + AsyncRemediationsResourceWithRawResponse, + RemediationsResourceWithStreamingResponse, + AsyncRemediationsResourceWithStreamingResponse, +) +from .expert_answers import ( + ExpertAnswersResource, + AsyncExpertAnswersResource, + ExpertAnswersResourceWithRawResponse, + AsyncExpertAnswersResourceWithRawResponse, + ExpertAnswersResourceWithStreamingResponse, + AsyncExpertAnswersResourceWithStreamingResponse, +) + +__all__ = [ + "ExpertAnswersResource", + "AsyncExpertAnswersResource", + "ExpertAnswersResourceWithRawResponse", + "AsyncExpertAnswersResourceWithRawResponse", + "ExpertAnswersResourceWithStreamingResponse", + "AsyncExpertAnswersResourceWithStreamingResponse", + "RemediationsResource", + "AsyncRemediationsResource", + "RemediationsResourceWithRawResponse", + "AsyncRemediationsResourceWithRawResponse", + "RemediationsResourceWithStreamingResponse", + "AsyncRemediationsResourceWithStreamingResponse", +] diff --git a/src/codex/resources/projects/remediations/expert_answers.py b/src/codex/resources/projects/remediations/expert_answers.py new file mode 100644 index 00000000..bade0787 --- /dev/null +++ b/src/codex/resources/projects/remediations/expert_answers.py @@ -0,0 +1,968 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ....pagination import SyncOffsetPageExpertAnswers, AsyncOffsetPageExpertAnswers +from ...._base_client import AsyncPaginator, make_request_options +from ....types.projects.remediations import ( + expert_answer_list_params, + expert_answer_create_params, + expert_answer_edit_answer_params, + expert_answer_edit_draft_answer_params, +) +from ....types.projects.remediations.expert_answer_list_response import ExpertAnswerListResponse +from ....types.projects.remediations.expert_answer_pause_response import ExpertAnswerPauseResponse +from ....types.projects.remediations.expert_answer_create_response import ExpertAnswerCreateResponse +from ....types.projects.remediations.expert_answer_publish_response import ExpertAnswerPublishResponse +from ....types.projects.remediations.expert_answer_unpause_response import ExpertAnswerUnpauseResponse +from ....types.projects.remediations.expert_answer_retrieve_response import ExpertAnswerRetrieveResponse +from ....types.projects.remediations.expert_answer_edit_answer_response import ExpertAnswerEditAnswerResponse +from ....types.projects.remediations.expert_answer_edit_draft_answer_response import ExpertAnswerEditDraftAnswerResponse + +__all__ = ["ExpertAnswersResource", "AsyncExpertAnswersResource"] + + +class ExpertAnswersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ExpertAnswersResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return ExpertAnswersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExpertAnswersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return ExpertAnswersResourceWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + query: str, + answer: Optional[str] | Omit = omit, + draft_answer: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExpertAnswerCreateResponse: + """ + Create Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/expert_answers/", + body=maybe_transform( + { + "query": query, + "answer": answer, + "draft_answer": draft_answer, + }, + expert_answer_create_params.ExpertAnswerCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerCreateResponse, + ) + + def retrieve( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerRetrieveResponse: + """ + Get Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return self._get( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerRetrieveResponse, + ) + + def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_at_end: Union[str, datetime, None] | Omit = omit, + last_edited_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_by: Optional[str] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | Omit = omit, + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | 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, + ) -> SyncOffsetPageExpertAnswers[ExpertAnswerListResponse]: + """ + List Expert Answers Route + + Args: + created_at_end: Filter remediations created at or before this timestamp + + created_at_start: Filter remediations created at or after this timestamp + + last_edited_at_end: Filter remediations last edited at or before this timestamp + + last_edited_at_start: Filter remediations last edited at or after this timestamp + + last_edited_by: Filter by last edited by user ID + + status: Filter expert answers that have ANY of these statuses (OR operation) + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + f"/api/projects/{project_id}/expert_answers/", + page=SyncOffsetPageExpertAnswers[ExpertAnswerListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "last_edited_at_end": last_edited_at_end, + "last_edited_at_start": last_edited_at_start, + "last_edited_by": last_edited_by, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "status": status, + }, + expert_answer_list_params.ExpertAnswerListParams, + ), + ), + model=ExpertAnswerListResponse, + ) + + def delete( + self, + expert_answer_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def edit_answer( + self, + expert_answer_id: str, + *, + project_id: str, + answer: 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, + ) -> ExpertAnswerEditAnswerResponse: + """ + Edit Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/edit_expert_answer", + body=maybe_transform({"answer": answer}, expert_answer_edit_answer_params.ExpertAnswerEditAnswerParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerEditAnswerResponse, + ) + + def edit_draft_answer( + self, + expert_answer_id: str, + *, + project_id: str, + draft_answer: 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, + ) -> ExpertAnswerEditDraftAnswerResponse: + """ + Edit Draft Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/edit_draft_expert_answer", + body=maybe_transform( + {"draft_answer": draft_answer}, expert_answer_edit_draft_answer_params.ExpertAnswerEditDraftAnswerParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerEditDraftAnswerResponse, + ) + + def pause( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerPauseResponse: + """ + Pause Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerPauseResponse, + ) + + def publish( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerPublishResponse: + """ + Publish Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/publish", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerPublishResponse, + ) + + def unpause( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerUnpauseResponse: + """ + Unpause Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerUnpauseResponse, + ) + + +class AsyncExpertAnswersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExpertAnswersResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncExpertAnswersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExpertAnswersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncExpertAnswersResourceWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + query: str, + answer: Optional[str] | Omit = omit, + draft_answer: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExpertAnswerCreateResponse: + """ + Create Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/expert_answers/", + body=await async_maybe_transform( + { + "query": query, + "answer": answer, + "draft_answer": draft_answer, + }, + expert_answer_create_params.ExpertAnswerCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerCreateResponse, + ) + + async def retrieve( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerRetrieveResponse: + """ + Get Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return await self._get( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerRetrieveResponse, + ) + + def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_at_end: Union[str, datetime, None] | Omit = omit, + last_edited_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_by: Optional[str] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | Omit = omit, + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ExpertAnswerListResponse, AsyncOffsetPageExpertAnswers[ExpertAnswerListResponse]]: + """ + List Expert Answers Route + + Args: + created_at_end: Filter remediations created at or before this timestamp + + created_at_start: Filter remediations created at or after this timestamp + + last_edited_at_end: Filter remediations last edited at or before this timestamp + + last_edited_at_start: Filter remediations last edited at or after this timestamp + + last_edited_by: Filter by last edited by user ID + + status: Filter expert answers that have ANY of these statuses (OR operation) + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + f"/api/projects/{project_id}/expert_answers/", + page=AsyncOffsetPageExpertAnswers[ExpertAnswerListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "last_edited_at_end": last_edited_at_end, + "last_edited_at_start": last_edited_at_start, + "last_edited_by": last_edited_by, + "limit": limit, + "offset": offset, + "order": order, + "sort": sort, + "status": status, + }, + expert_answer_list_params.ExpertAnswerListParams, + ), + ), + model=ExpertAnswerListResponse, + ) + + async def delete( + self, + expert_answer_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def edit_answer( + self, + expert_answer_id: str, + *, + project_id: str, + answer: 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, + ) -> ExpertAnswerEditAnswerResponse: + """ + Edit Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return await self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/edit_expert_answer", + body=await async_maybe_transform( + {"answer": answer}, expert_answer_edit_answer_params.ExpertAnswerEditAnswerParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerEditAnswerResponse, + ) + + async def edit_draft_answer( + self, + expert_answer_id: str, + *, + project_id: str, + draft_answer: 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, + ) -> ExpertAnswerEditDraftAnswerResponse: + """ + Edit Draft Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return await self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/edit_draft_expert_answer", + body=await async_maybe_transform( + {"draft_answer": draft_answer}, expert_answer_edit_draft_answer_params.ExpertAnswerEditDraftAnswerParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerEditDraftAnswerResponse, + ) + + async def pause( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerPauseResponse: + """ + Pause Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return await self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerPauseResponse, + ) + + async def publish( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerPublishResponse: + """ + Publish Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return await self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/publish", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerPublishResponse, + ) + + async def unpause( + self, + expert_answer_id: str, + *, + project_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, + ) -> ExpertAnswerUnpauseResponse: + """ + Unpause Expert Answer Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_answer_id: + raise ValueError(f"Expected a non-empty value for `expert_answer_id` but received {expert_answer_id!r}") + return await self._patch( + f"/api/projects/{project_id}/expert_answers/{expert_answer_id}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertAnswerUnpauseResponse, + ) + + +class ExpertAnswersResourceWithRawResponse: + def __init__(self, expert_answers: ExpertAnswersResource) -> None: + self._expert_answers = expert_answers + + self.create = to_raw_response_wrapper( + expert_answers.create, + ) + self.retrieve = to_raw_response_wrapper( + expert_answers.retrieve, + ) + self.list = to_raw_response_wrapper( + expert_answers.list, + ) + self.delete = to_raw_response_wrapper( + expert_answers.delete, + ) + self.edit_answer = to_raw_response_wrapper( + expert_answers.edit_answer, + ) + self.edit_draft_answer = to_raw_response_wrapper( + expert_answers.edit_draft_answer, + ) + self.pause = to_raw_response_wrapper( + expert_answers.pause, + ) + self.publish = to_raw_response_wrapper( + expert_answers.publish, + ) + self.unpause = to_raw_response_wrapper( + expert_answers.unpause, + ) + + +class AsyncExpertAnswersResourceWithRawResponse: + def __init__(self, expert_answers: AsyncExpertAnswersResource) -> None: + self._expert_answers = expert_answers + + self.create = async_to_raw_response_wrapper( + expert_answers.create, + ) + self.retrieve = async_to_raw_response_wrapper( + expert_answers.retrieve, + ) + self.list = async_to_raw_response_wrapper( + expert_answers.list, + ) + self.delete = async_to_raw_response_wrapper( + expert_answers.delete, + ) + self.edit_answer = async_to_raw_response_wrapper( + expert_answers.edit_answer, + ) + self.edit_draft_answer = async_to_raw_response_wrapper( + expert_answers.edit_draft_answer, + ) + self.pause = async_to_raw_response_wrapper( + expert_answers.pause, + ) + self.publish = async_to_raw_response_wrapper( + expert_answers.publish, + ) + self.unpause = async_to_raw_response_wrapper( + expert_answers.unpause, + ) + + +class ExpertAnswersResourceWithStreamingResponse: + def __init__(self, expert_answers: ExpertAnswersResource) -> None: + self._expert_answers = expert_answers + + self.create = to_streamed_response_wrapper( + expert_answers.create, + ) + self.retrieve = to_streamed_response_wrapper( + expert_answers.retrieve, + ) + self.list = to_streamed_response_wrapper( + expert_answers.list, + ) + self.delete = to_streamed_response_wrapper( + expert_answers.delete, + ) + self.edit_answer = to_streamed_response_wrapper( + expert_answers.edit_answer, + ) + self.edit_draft_answer = to_streamed_response_wrapper( + expert_answers.edit_draft_answer, + ) + self.pause = to_streamed_response_wrapper( + expert_answers.pause, + ) + self.publish = to_streamed_response_wrapper( + expert_answers.publish, + ) + self.unpause = to_streamed_response_wrapper( + expert_answers.unpause, + ) + + +class AsyncExpertAnswersResourceWithStreamingResponse: + def __init__(self, expert_answers: AsyncExpertAnswersResource) -> None: + self._expert_answers = expert_answers + + self.create = async_to_streamed_response_wrapper( + expert_answers.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + expert_answers.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + expert_answers.list, + ) + self.delete = async_to_streamed_response_wrapper( + expert_answers.delete, + ) + self.edit_answer = async_to_streamed_response_wrapper( + expert_answers.edit_answer, + ) + self.edit_draft_answer = async_to_streamed_response_wrapper( + expert_answers.edit_draft_answer, + ) + self.pause = async_to_streamed_response_wrapper( + expert_answers.pause, + ) + self.publish = async_to_streamed_response_wrapper( + expert_answers.publish, + ) + self.unpause = async_to_streamed_response_wrapper( + expert_answers.unpause, + ) diff --git a/src/codex/resources/projects/remediations.py b/src/codex/resources/projects/remediations/remediations.py similarity index 95% rename from src/codex/resources/projects/remediations.py rename to src/codex/resources/projects/remediations/remediations.py index 8fc26f0b..23873afb 100644 --- a/src/codex/resources/projects/remediations.py +++ b/src/codex/resources/projects/remediations/remediations.py @@ -9,39 +9,51 @@ import httpx -from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( +from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ...pagination import SyncOffsetPageRemediations, AsyncOffsetPageRemediations -from ..._base_client import AsyncPaginator, make_request_options -from ...types.projects import ( +from ....pagination import SyncOffsetPageRemediations, AsyncOffsetPageRemediations +from .expert_answers import ( + ExpertAnswersResource, + AsyncExpertAnswersResource, + ExpertAnswersResourceWithRawResponse, + AsyncExpertAnswersResourceWithRawResponse, + ExpertAnswersResourceWithStreamingResponse, + AsyncExpertAnswersResourceWithStreamingResponse, +) +from ...._base_client import AsyncPaginator, make_request_options +from ....types.projects import ( remediation_list_params, remediation_create_params, remediation_edit_answer_params, remediation_edit_draft_answer_params, ) -from ...types.projects.remediation_list_response import RemediationListResponse -from ...types.projects.remediation_pause_response import RemediationPauseResponse -from ...types.projects.remediation_create_response import RemediationCreateResponse -from ...types.projects.remediation_publish_response import RemediationPublishResponse -from ...types.projects.remediation_unpause_response import RemediationUnpauseResponse -from ...types.projects.remediation_retrieve_response import RemediationRetrieveResponse -from ...types.projects.remediation_edit_answer_response import RemediationEditAnswerResponse -from ...types.projects.remediation_edit_draft_answer_response import RemediationEditDraftAnswerResponse -from ...types.projects.remediation_list_resolved_logs_response import RemediationListResolvedLogsResponse -from ...types.projects.remediation_get_resolved_logs_count_response import RemediationGetResolvedLogsCountResponse +from ....types.projects.remediation_list_response import RemediationListResponse +from ....types.projects.remediation_pause_response import RemediationPauseResponse +from ....types.projects.remediation_create_response import RemediationCreateResponse +from ....types.projects.remediation_publish_response import RemediationPublishResponse +from ....types.projects.remediation_unpause_response import RemediationUnpauseResponse +from ....types.projects.remediation_retrieve_response import RemediationRetrieveResponse +from ....types.projects.remediation_edit_answer_response import RemediationEditAnswerResponse +from ....types.projects.remediation_edit_draft_answer_response import RemediationEditDraftAnswerResponse +from ....types.projects.remediation_list_resolved_logs_response import RemediationListResolvedLogsResponse +from ....types.projects.remediation_get_resolved_logs_count_response import RemediationGetResolvedLogsCountResponse __all__ = ["RemediationsResource", "AsyncRemediationsResource"] class RemediationsResource(SyncAPIResource): + @cached_property + def expert_answers(self) -> ExpertAnswersResource: + return ExpertAnswersResource(self._client) + @cached_property def with_raw_response(self) -> RemediationsResourceWithRawResponse: """ @@ -523,6 +535,10 @@ def unpause( class AsyncRemediationsResource(AsyncAPIResource): + @cached_property + def expert_answers(self) -> AsyncExpertAnswersResource: + return AsyncExpertAnswersResource(self._client) + @cached_property def with_raw_response(self) -> AsyncRemediationsResourceWithRawResponse: """ @@ -1065,6 +1081,10 @@ def __init__(self, remediations: RemediationsResource) -> None: ) ) + @cached_property + def expert_answers(self) -> ExpertAnswersResourceWithRawResponse: + return ExpertAnswersResourceWithRawResponse(self._remediations.expert_answers) + class AsyncRemediationsResourceWithRawResponse: def __init__(self, remediations: AsyncRemediationsResource) -> None: @@ -1126,6 +1146,10 @@ def __init__(self, remediations: AsyncRemediationsResource) -> None: ) ) + @cached_property + def expert_answers(self) -> AsyncExpertAnswersResourceWithRawResponse: + return AsyncExpertAnswersResourceWithRawResponse(self._remediations.expert_answers) + class RemediationsResourceWithStreamingResponse: def __init__(self, remediations: RemediationsResource) -> None: @@ -1187,6 +1211,10 @@ def __init__(self, remediations: RemediationsResource) -> None: ) ) + @cached_property + def expert_answers(self) -> ExpertAnswersResourceWithStreamingResponse: + return ExpertAnswersResourceWithStreamingResponse(self._remediations.expert_answers) + class AsyncRemediationsResourceWithStreamingResponse: def __init__(self, remediations: AsyncRemediationsResource) -> None: @@ -1247,3 +1275,7 @@ def __init__(self, remediations: AsyncRemediationsResource) -> None: remediations.unpause, # pyright: ignore[reportDeprecated], ) ) + + @cached_property + def expert_answers(self) -> AsyncExpertAnswersResourceWithStreamingResponse: + return AsyncExpertAnswersResourceWithStreamingResponse(self._remediations.expert_answers) diff --git a/src/codex/types/projects/remediations/__init__.py b/src/codex/types/projects/remediations/__init__.py new file mode 100644 index 00000000..da142200 --- /dev/null +++ b/src/codex/types/projects/remediations/__init__.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .expert_answer_list_params import ExpertAnswerListParams as ExpertAnswerListParams +from .expert_answer_create_params import ExpertAnswerCreateParams as ExpertAnswerCreateParams +from .expert_answer_list_response import ExpertAnswerListResponse as ExpertAnswerListResponse +from .expert_answer_pause_response import ExpertAnswerPauseResponse as ExpertAnswerPauseResponse +from .expert_answer_create_response import ExpertAnswerCreateResponse as ExpertAnswerCreateResponse +from .expert_answer_publish_response import ExpertAnswerPublishResponse as ExpertAnswerPublishResponse +from .expert_answer_unpause_response import ExpertAnswerUnpauseResponse as ExpertAnswerUnpauseResponse +from .expert_answer_retrieve_response import ExpertAnswerRetrieveResponse as ExpertAnswerRetrieveResponse +from .expert_answer_edit_answer_params import ExpertAnswerEditAnswerParams as ExpertAnswerEditAnswerParams +from .expert_answer_edit_answer_response import ExpertAnswerEditAnswerResponse as ExpertAnswerEditAnswerResponse +from .expert_answer_edit_draft_answer_params import ( + ExpertAnswerEditDraftAnswerParams as ExpertAnswerEditDraftAnswerParams, +) +from .expert_answer_edit_draft_answer_response import ( + ExpertAnswerEditDraftAnswerResponse as ExpertAnswerEditDraftAnswerResponse, +) diff --git a/src/codex/types/projects/remediations/expert_answer_create_params.py b/src/codex/types/projects/remediations/expert_answer_create_params.py new file mode 100644 index 00000000..34c33167 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_create_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ExpertAnswerCreateParams"] + + +class ExpertAnswerCreateParams(TypedDict, total=False): + query: Required[str] + + answer: Optional[str] + + draft_answer: Optional[str] diff --git a/src/codex/types/projects/remediations/expert_answer_create_response.py b/src/codex/types/projects/remediations/expert_answer_create_response.py new file mode 100644 index 00000000..b58d033f --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_create_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerCreateResponse"] + + +class ExpertAnswerCreateResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/src/codex/types/projects/remediations/expert_answer_edit_answer_params.py b/src/codex/types/projects/remediations/expert_answer_edit_answer_params.py new file mode 100644 index 00000000..44e75c9e --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_edit_answer_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ExpertAnswerEditAnswerParams"] + + +class ExpertAnswerEditAnswerParams(TypedDict, total=False): + project_id: Required[str] + + answer: Required[str] diff --git a/src/codex/types/projects/remediations/expert_answer_edit_answer_response.py b/src/codex/types/projects/remediations/expert_answer_edit_answer_response.py new file mode 100644 index 00000000..2e3d5589 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_edit_answer_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerEditAnswerResponse"] + + +class ExpertAnswerEditAnswerResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/src/codex/types/projects/remediations/expert_answer_edit_draft_answer_params.py b/src/codex/types/projects/remediations/expert_answer_edit_draft_answer_params.py new file mode 100644 index 00000000..3b3ab8b7 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_edit_draft_answer_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ExpertAnswerEditDraftAnswerParams"] + + +class ExpertAnswerEditDraftAnswerParams(TypedDict, total=False): + project_id: Required[str] + + draft_answer: Required[str] diff --git a/src/codex/types/projects/remediations/expert_answer_edit_draft_answer_response.py b/src/codex/types/projects/remediations/expert_answer_edit_draft_answer_response.py new file mode 100644 index 00000000..7872526e --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_edit_draft_answer_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerEditDraftAnswerResponse"] + + +class ExpertAnswerEditDraftAnswerResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/src/codex/types/projects/remediations/expert_answer_list_params.py b/src/codex/types/projects/remediations/expert_answer_list_params.py new file mode 100644 index 00000000..3a9fd1ec --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_list_params.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["ExpertAnswerListParams"] + + +class ExpertAnswerListParams(TypedDict, total=False): + created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations created at or before this timestamp""" + + created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations created at or after this timestamp""" + + last_edited_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations last edited at or before this timestamp""" + + last_edited_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter remediations last edited at or after this timestamp""" + + last_edited_by: Optional[str] + """Filter by last edited by user ID""" + + limit: int + + offset: int + + order: Literal["asc", "desc"] + + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] + + status: Optional[List[Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"]]] + """Filter expert answers that have ANY of these statuses (OR operation)""" diff --git a/src/codex/types/projects/remediations/expert_answer_list_response.py b/src/codex/types/projects/remediations/expert_answer_list_response.py new file mode 100644 index 00000000..8eabb3f6 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_list_response.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerListResponse"] + + +class ExpertAnswerListResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + resolved_logs_count: int + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/src/codex/types/projects/remediations/expert_answer_pause_response.py b/src/codex/types/projects/remediations/expert_answer_pause_response.py new file mode 100644 index 00000000..a2c9a0a8 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_pause_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerPauseResponse"] + + +class ExpertAnswerPauseResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/src/codex/types/projects/remediations/expert_answer_publish_response.py b/src/codex/types/projects/remediations/expert_answer_publish_response.py new file mode 100644 index 00000000..547a0361 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_publish_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerPublishResponse"] + + +class ExpertAnswerPublishResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/src/codex/types/projects/remediations/expert_answer_retrieve_response.py b/src/codex/types/projects/remediations/expert_answer_retrieve_response.py new file mode 100644 index 00000000..b1da431c --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_retrieve_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerRetrieveResponse"] + + +class ExpertAnswerRetrieveResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/src/codex/types/projects/remediations/expert_answer_unpause_response.py b/src/codex/types/projects/remediations/expert_answer_unpause_response.py new file mode 100644 index 00000000..a1602845 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_answer_unpause_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertAnswerUnpauseResponse"] + + +class ExpertAnswerUnpauseResponse(BaseModel): + id: str + + answered_at: Optional[datetime] = None + + answered_by: Optional[str] = None + + created_at: datetime + + last_edited_at: Optional[datetime] = None + + last_edited_by: Optional[str] = None + + needs_review: bool + + project_id: str + + query: str + + status: Literal["ACTIVE", "DRAFT", "ACTIVE_WITH_DRAFT", "PAUSED"] + + answer: Optional[str] = None + + draft_answer: Optional[str] = None + + issue_id: Optional[str] = None + + manual_review_status_override: Optional[Literal["addressed", "unaddressed"]] = None + """Manual review status override for issues.""" diff --git a/tests/api_resources/projects/remediations/__init__.py b/tests/api_resources/projects/remediations/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/projects/remediations/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/projects/remediations/test_expert_answers.py b/tests/api_resources/projects/remediations/test_expert_answers.py new file mode 100644 index 00000000..d0757e98 --- /dev/null +++ b/tests/api_resources/projects/remediations/test_expert_answers.py @@ -0,0 +1,1019 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex._utils import parse_datetime +from codex.pagination import SyncOffsetPageExpertAnswers, AsyncOffsetPageExpertAnswers +from codex.types.projects.remediations import ( + ExpertAnswerListResponse, + ExpertAnswerPauseResponse, + ExpertAnswerCreateResponse, + ExpertAnswerPublishResponse, + ExpertAnswerUnpauseResponse, + ExpertAnswerRetrieveResponse, + ExpertAnswerEditAnswerResponse, + ExpertAnswerEditDraftAnswerResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExpertAnswers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + ) + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + answer="answer", + draft_answer="draft_answer", + ) + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_create(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.create( + project_id="", + query="x", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerRetrieveResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(ExpertAnswerRetrieveResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(ExpertAnswerRetrieveResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.retrieve( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + sort="created_at", + status=["ACTIVE"], + ) + assert_matches_type(SyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(SyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(SyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert expert_answer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert expert_answer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert expert_answer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.delete( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_edit_answer(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + assert_matches_type(ExpertAnswerEditAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_edit_answer(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(ExpertAnswerEditAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_edit_answer(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(ExpertAnswerEditAnswerResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_edit_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + answer="answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.edit_answer( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_edit_draft_answer(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + assert_matches_type(ExpertAnswerEditDraftAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_edit_draft_answer(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(ExpertAnswerEditDraftAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_edit_draft_answer(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(ExpertAnswerEditDraftAnswerResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_edit_draft_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + draft_answer="draft_answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.edit_draft_answer( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_pause(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerPauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_pause(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(ExpertAnswerPauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_pause(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(ExpertAnswerPauseResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_pause(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.pause( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_publish(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerPublishResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_publish(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(ExpertAnswerPublishResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_publish(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(ExpertAnswerPublishResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_publish(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.publish( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_unpause(self, client: Codex) -> None: + expert_answer = client.projects.remediations.expert_answers.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerUnpauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_unpause(self, client: Codex) -> None: + response = client.projects.remediations.expert_answers.with_raw_response.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = response.parse() + assert_matches_type(ExpertAnswerUnpauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_unpause(self, client: Codex) -> None: + with client.projects.remediations.expert_answers.with_streaming_response.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = response.parse() + assert_matches_type(ExpertAnswerUnpauseResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_unpause(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + client.projects.remediations.expert_answers.with_raw_response.unpause( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncExpertAnswers: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + ) + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + answer="answer", + draft_answer="draft_answer", + ) + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + query="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerCreateResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_create(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.create( + project_id="", + query="x", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerRetrieveResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerRetrieveResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerRetrieveResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.retrieve( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.retrieve( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + sort="created_at", + status=["ACTIVE"], + ) + assert_matches_type(AsyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(AsyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type( + AsyncOffsetPageExpertAnswers[ExpertAnswerListResponse], expert_answer, path=["response"] + ) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert expert_answer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert expert_answer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert expert_answer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.delete( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.delete( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_edit_answer(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + assert_matches_type(ExpertAnswerEditAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_edit_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerEditAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_edit_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerEditAnswerResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_edit_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.edit_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + answer="answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.edit_answer( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + answer="answer", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_edit_draft_answer(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + assert_matches_type(ExpertAnswerEditDraftAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerEditDraftAnswerResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_edit_draft_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerEditDraftAnswerResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_edit_draft_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.edit_draft_answer( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + draft_answer="draft_answer", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.edit_draft_answer( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + draft_answer="draft_answer", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_pause(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerPauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_pause(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerPauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_pause(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerPauseResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_pause(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.pause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.pause( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_publish(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerPublishResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_publish(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerPublishResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_publish(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerPublishResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_publish(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.publish( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.publish( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_unpause(self, async_client: AsyncCodex) -> None: + expert_answer = await async_client.projects.remediations.expert_answers.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertAnswerUnpauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_unpause(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_answers.with_raw_response.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerUnpauseResponse, expert_answer, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_unpause(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_answers.with_streaming_response.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_answer = await response.parse() + assert_matches_type(ExpertAnswerUnpauseResponse, expert_answer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_unpause(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.unpause( + expert_answer_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_answer_id` but received ''"): + await async_client.projects.remediations.expert_answers.with_raw_response.unpause( + expert_answer_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) From d754aaab4dfe3a4cb029be77fbb31f63919eb7f1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 03:17:41 +0000 Subject: [PATCH 298/320] feat(api): api update --- .stats.yml | 2 +- .../types/projects/query_log_list_by_group_response.py | 6 ++++++ src/codex/types/projects/query_log_list_groups_response.py | 6 ++++++ src/codex/types/projects/query_log_list_response.py | 6 ++++++ src/codex/types/projects/query_log_retrieve_response.py | 6 ++++++ .../projects/remediation_list_resolved_logs_response.py | 6 ++++++ 6 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 49a4e3c8..36d8a0f0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 65 -openapi_spec_hash: d273ca5158facc1251efa0a5f9e723c5 +openapi_spec_hash: 9018ebfb2a9e1afa87058b3a4bd41b0b config_hash: cd9208a2204f43e0aa5ab35ac85ef90d diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index ed1a32d0..1a89baa8 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -419,6 +419,9 @@ class QueryLogsByGroupQueryLog(BaseModel): applied_expert_answer_id: Optional[str] = None """ID of the expert answer that was applied to the query.""" + applied_expert_review_id: Optional[str] = None + """ID of the expert review that was applied to the query.""" + context: Optional[List[QueryLogsByGroupQueryLogContext]] = None """RAG context used for the query""" @@ -479,6 +482,9 @@ class QueryLogsByGroupQueryLog(BaseModel): expert_review_explanation: Optional[str] = None """Expert explanation when marked as bad""" + expert_review_id: Optional[str] = None + """ID of the expert review that was created for the query.""" + expert_review_status: Optional[Literal["good", "bad"]] = None """Expert review status: 'good' or 'bad'""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index d20eb8c3..fe70223b 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -414,6 +414,9 @@ class QueryLogListGroupsResponse(BaseModel): applied_expert_answer_id: Optional[str] = None """ID of the expert answer that was applied to the query.""" + applied_expert_review_id: Optional[str] = None + """ID of the expert review that was applied to the query.""" + context: Optional[List[Context]] = None """RAG context used for the query""" @@ -474,6 +477,9 @@ class QueryLogListGroupsResponse(BaseModel): expert_review_explanation: Optional[str] = None """Expert explanation when marked as bad""" + expert_review_id: Optional[str] = None + """ID of the expert review that was created for the query.""" + expert_review_status: Optional[Literal["good", "bad"]] = None """Expert review status: 'good' or 'bad'""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index 9d8d1985..dc7768f7 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -402,6 +402,9 @@ class QueryLogListResponse(BaseModel): applied_expert_answer_id: Optional[str] = None """ID of the expert answer that was applied to the query.""" + applied_expert_review_id: Optional[str] = None + """ID of the expert review that was applied to the query.""" + context: Optional[List[Context]] = None """RAG context used for the query""" @@ -462,6 +465,9 @@ class QueryLogListResponse(BaseModel): expert_review_explanation: Optional[str] = None """Expert explanation when marked as bad""" + expert_review_id: Optional[str] = None + """ID of the expert review that was created for the query.""" + expert_review_status: Optional[Literal["good", "bad"]] = None """Expert review status: 'good' or 'bad'""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 3943325c..db919432 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -409,6 +409,9 @@ class QueryLogRetrieveResponse(BaseModel): applied_expert_answer_id: Optional[str] = None """ID of the expert answer that was applied to the query.""" + applied_expert_review_id: Optional[str] = None + """ID of the expert review that was applied to the query.""" + context: Optional[List[Context]] = None """RAG context used for the query""" @@ -469,6 +472,9 @@ class QueryLogRetrieveResponse(BaseModel): expert_review_explanation: Optional[str] = None """Expert explanation when marked as bad""" + expert_review_id: Optional[str] = None + """ID of the expert review that was created for the query.""" + expert_review_status: Optional[Literal["good", "bad"]] = None """Expert review status: 'good' or 'bad'""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index e586b142..9f1b77b4 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -409,6 +409,9 @@ class QueryLog(BaseModel): applied_expert_answer_id: Optional[str] = None """ID of the expert answer that was applied to the query.""" + applied_expert_review_id: Optional[str] = None + """ID of the expert review that was applied to the query.""" + context: Optional[List[QueryLogContext]] = None """RAG context used for the query""" @@ -469,6 +472,9 @@ class QueryLog(BaseModel): expert_review_explanation: Optional[str] = None """Expert explanation when marked as bad""" + expert_review_id: Optional[str] = None + """ID of the expert review that was created for the query.""" + expert_review_status: Optional[Literal["good", "bad"]] = None """Expert review status: 'good' or 'bad'""" From d493825e02f38fb41d10d6fdc1a4522fdf02fbd5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:07:15 +0000 Subject: [PATCH 299/320] feat(api): add expert review endpoints --- .stats.yml | 4 +- api.md | 21 + src/codex/pagination.py | 62 ++ .../projects/remediations/__init__.py | 14 + .../projects/remediations/expert_reviews.py | 682 ++++++++++++++++++ .../projects/remediations/remediations.py | 32 + .../types/projects/remediations/__init__.py | 8 + .../expert_review_create_params.py | 21 + .../expert_review_create_response.py | 29 + .../expert_review_delete_params.py | 14 + .../remediations/expert_review_edit_params.py | 16 + .../expert_review_edit_response.py | 29 + .../remediations/expert_review_list_params.py | 41 ++ .../expert_review_list_response.py | 35 + .../expert_review_retrieve_response.py | 35 + .../remediations/test_expert_reviews.py | 631 ++++++++++++++++ 16 files changed, 1672 insertions(+), 2 deletions(-) create mode 100644 src/codex/resources/projects/remediations/expert_reviews.py create mode 100644 src/codex/types/projects/remediations/expert_review_create_params.py create mode 100644 src/codex/types/projects/remediations/expert_review_create_response.py create mode 100644 src/codex/types/projects/remediations/expert_review_delete_params.py create mode 100644 src/codex/types/projects/remediations/expert_review_edit_params.py create mode 100644 src/codex/types/projects/remediations/expert_review_edit_response.py create mode 100644 src/codex/types/projects/remediations/expert_review_list_params.py create mode 100644 src/codex/types/projects/remediations/expert_review_list_response.py create mode 100644 src/codex/types/projects/remediations/expert_review_retrieve_response.py create mode 100644 tests/api_resources/projects/remediations/test_expert_reviews.py diff --git a/.stats.yml b/.stats.yml index 36d8a0f0..c021c17a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 65 +configured_endpoints: 70 openapi_spec_hash: 9018ebfb2a9e1afa87058b3a4bd41b0b -config_hash: cd9208a2204f43e0aa5ab35ac85ef90d +config_hash: aad16f20fed13ac50211fc1d0e2ea621 diff --git a/api.md b/api.md index 6e8c0535..4ee79642 100644 --- a/api.md +++ b/api.md @@ -284,3 +284,24 @@ Methods: - client.projects.remediations.expert_answers.pause(expert_answer_id, \*, project_id) -> ExpertAnswerPauseResponse - client.projects.remediations.expert_answers.publish(expert_answer_id, \*, project_id) -> ExpertAnswerPublishResponse - client.projects.remediations.expert_answers.unpause(expert_answer_id, \*, project_id) -> ExpertAnswerUnpauseResponse + +### ExpertReviews + +Types: + +```python +from codex.types.projects.remediations import ( + ExpertReviewCreateResponse, + ExpertReviewRetrieveResponse, + ExpertReviewListResponse, + ExpertReviewEditResponse, +) +``` + +Methods: + +- client.projects.remediations.expert_reviews.create(project_id, \*\*params) -> ExpertReviewCreateResponse +- client.projects.remediations.expert_reviews.retrieve(expert_review_id, \*, project_id) -> ExpertReviewRetrieveResponse +- client.projects.remediations.expert_reviews.list(project_id, \*\*params) -> SyncOffsetPageExpertReviews[ExpertReviewListResponse] +- client.projects.remediations.expert_reviews.delete(expert_review_id, \*, project_id, \*\*params) -> None +- client.projects.remediations.expert_reviews.edit(expert_review_id, \*, project_id, \*\*params) -> ExpertReviewEditResponse diff --git a/src/codex/pagination.py b/src/codex/pagination.py index e5721eb9..d44ce476 100644 --- a/src/codex/pagination.py +++ b/src/codex/pagination.py @@ -24,6 +24,8 @@ "AsyncOffsetPageQueryLogsByGroup", "SyncOffsetPageExpertAnswers", "AsyncOffsetPageExpertAnswers", + "SyncOffsetPageExpertReviews", + "AsyncOffsetPageExpertReviews", ] _BaseModelT = TypeVar("_BaseModelT", bound=BaseModel) @@ -451,3 +453,63 @@ def next_page_info(self) -> Optional[PageInfo]: return PageInfo(params={"offset": current_count}) return None + + +class SyncOffsetPageExpertReviews(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + expert_reviews: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + expert_reviews = self.expert_reviews + if not expert_reviews: + return [] + return expert_reviews + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None + + +class AsyncOffsetPageExpertReviews(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + expert_reviews: List[_T] + total_count: Optional[int] = None + + @override + def _get_page_items(self) -> List[_T]: + expert_reviews = self.expert_reviews + if not expert_reviews: + return [] + return expert_reviews + + @override + def next_page_info(self) -> Optional[PageInfo]: + offset = self._options.params.get("offset") or 0 + if not isinstance(offset, int): + raise ValueError(f'Expected "offset" param to be an integer but got {offset}') + + length = len(self._get_page_items()) + current_count = offset + length + + total_count = self.total_count + if total_count is None: + return None + + if current_count < total_count: + return PageInfo(params={"offset": current_count}) + + return None diff --git a/src/codex/resources/projects/remediations/__init__.py b/src/codex/resources/projects/remediations/__init__.py index 3a7c4f9e..6c71aad5 100644 --- a/src/codex/resources/projects/remediations/__init__.py +++ b/src/codex/resources/projects/remediations/__init__.py @@ -16,6 +16,14 @@ ExpertAnswersResourceWithStreamingResponse, AsyncExpertAnswersResourceWithStreamingResponse, ) +from .expert_reviews import ( + ExpertReviewsResource, + AsyncExpertReviewsResource, + ExpertReviewsResourceWithRawResponse, + AsyncExpertReviewsResourceWithRawResponse, + ExpertReviewsResourceWithStreamingResponse, + AsyncExpertReviewsResourceWithStreamingResponse, +) __all__ = [ "ExpertAnswersResource", @@ -24,6 +32,12 @@ "AsyncExpertAnswersResourceWithRawResponse", "ExpertAnswersResourceWithStreamingResponse", "AsyncExpertAnswersResourceWithStreamingResponse", + "ExpertReviewsResource", + "AsyncExpertReviewsResource", + "ExpertReviewsResourceWithRawResponse", + "AsyncExpertReviewsResourceWithRawResponse", + "ExpertReviewsResourceWithStreamingResponse", + "AsyncExpertReviewsResourceWithStreamingResponse", "RemediationsResource", "AsyncRemediationsResource", "RemediationsResourceWithRawResponse", diff --git a/src/codex/resources/projects/remediations/expert_reviews.py b/src/codex/resources/projects/remediations/expert_reviews.py new file mode 100644 index 00000000..1d0db1ba --- /dev/null +++ b/src/codex/resources/projects/remediations/expert_reviews.py @@ -0,0 +1,682 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ....pagination import SyncOffsetPageExpertReviews, AsyncOffsetPageExpertReviews +from ...._base_client import AsyncPaginator, make_request_options +from ....types.projects.remediations import ( + expert_review_edit_params, + expert_review_list_params, + expert_review_create_params, + expert_review_delete_params, +) +from ....types.projects.remediations.expert_review_edit_response import ExpertReviewEditResponse +from ....types.projects.remediations.expert_review_list_response import ExpertReviewListResponse +from ....types.projects.remediations.expert_review_create_response import ExpertReviewCreateResponse +from ....types.projects.remediations.expert_review_retrieve_response import ExpertReviewRetrieveResponse + +__all__ = ["ExpertReviewsResource", "AsyncExpertReviewsResource"] + + +class ExpertReviewsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ExpertReviewsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return ExpertReviewsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExpertReviewsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return ExpertReviewsResourceWithStreamingResponse(self) + + def create( + self, + project_id: str, + *, + original_query_log_id: str, + review_status: Literal["good", "bad"], + generate_guidance: bool | Omit = omit, + explanation: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExpertReviewCreateResponse: + """ + Create Expert Review Route + + Args: + original_query_log_id: ID of the original query log + + review_status: Expert review status - 'good' or 'bad' + + explanation: Optional explanation for expert review + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/expert_reviews/", + body=maybe_transform( + { + "original_query_log_id": original_query_log_id, + "review_status": review_status, + "explanation": explanation, + }, + expert_review_create_params.ExpertReviewCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"generate_guidance": generate_guidance}, expert_review_create_params.ExpertReviewCreateParams + ), + ), + cast_to=ExpertReviewCreateResponse, + ) + + def retrieve( + self, + expert_review_id: str, + *, + project_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, + ) -> ExpertReviewRetrieveResponse: + """ + Get Expert Review Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_review_id: + raise ValueError(f"Expected a non-empty value for `expert_review_id` but received {expert_review_id!r}") + return self._get( + f"/api/projects/{project_id}/expert_reviews/{expert_review_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertReviewRetrieveResponse, + ) + + def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_at_end: Union[str, datetime, None] | Omit = omit, + last_edited_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_by: Optional[str] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + review_status: Optional[List[Literal["good", "bad"]]] | Omit = omit, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | 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, + ) -> SyncOffsetPageExpertReviews[ExpertReviewListResponse]: + """ + List Expert Reviews Route + + Args: + created_at_end: Filter expert reviews created at or before this timestamp + + created_at_start: Filter expert reviews created at or after this timestamp + + last_edited_at_end: Filter expert reviews last edited at or before this timestamp + + last_edited_at_start: Filter expert reviews last edited at or after this timestamp + + last_edited_by: Filter expert reviews last edited by user ID + + order: Sort order + + review_status: Filter expert reviews by review status + + sort: Sort expert reviews by field + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + f"/api/projects/{project_id}/expert_reviews/", + page=SyncOffsetPageExpertReviews[ExpertReviewListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "last_edited_at_end": last_edited_at_end, + "last_edited_at_start": last_edited_at_start, + "last_edited_by": last_edited_by, + "limit": limit, + "offset": offset, + "order": order, + "review_status": review_status, + "sort": sort, + }, + expert_review_list_params.ExpertReviewListParams, + ), + ), + model=ExpertReviewListResponse, + ) + + def delete( + self, + expert_review_id: str, + *, + project_id: str, + original_query_log_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Expert Review Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_review_id: + raise ValueError(f"Expected a non-empty value for `expert_review_id` but received {expert_review_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/api/projects/{project_id}/expert_reviews/{expert_review_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"original_query_log_id": original_query_log_id}, + expert_review_delete_params.ExpertReviewDeleteParams, + ), + ), + cast_to=NoneType, + ) + + def edit( + self, + expert_review_id: str, + *, + project_id: str, + explanation: Optional[str] | Omit = omit, + review_status: Optional[Literal["good", "bad"]] | 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, + ) -> ExpertReviewEditResponse: + """ + Update Expert Review Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_review_id: + raise ValueError(f"Expected a non-empty value for `expert_review_id` but received {expert_review_id!r}") + return self._patch( + f"/api/projects/{project_id}/expert_reviews/{expert_review_id}", + body=maybe_transform( + { + "explanation": explanation, + "review_status": review_status, + }, + expert_review_edit_params.ExpertReviewEditParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertReviewEditResponse, + ) + + +class AsyncExpertReviewsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExpertReviewsResourceWithRawResponse: + """ + 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/cleanlab/codex-python#accessing-raw-response-data-eg-headers + """ + return AsyncExpertReviewsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExpertReviewsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/cleanlab/codex-python#with_streaming_response + """ + return AsyncExpertReviewsResourceWithStreamingResponse(self) + + async def create( + self, + project_id: str, + *, + original_query_log_id: str, + review_status: Literal["good", "bad"], + generate_guidance: bool | Omit = omit, + explanation: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExpertReviewCreateResponse: + """ + Create Expert Review Route + + Args: + original_query_log_id: ID of the original query log + + review_status: Expert review status - 'good' or 'bad' + + explanation: Optional explanation for expert review + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/expert_reviews/", + body=await async_maybe_transform( + { + "original_query_log_id": original_query_log_id, + "review_status": review_status, + "explanation": explanation, + }, + expert_review_create_params.ExpertReviewCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"generate_guidance": generate_guidance}, expert_review_create_params.ExpertReviewCreateParams + ), + ), + cast_to=ExpertReviewCreateResponse, + ) + + async def retrieve( + self, + expert_review_id: str, + *, + project_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, + ) -> ExpertReviewRetrieveResponse: + """ + Get Expert Review Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_review_id: + raise ValueError(f"Expected a non-empty value for `expert_review_id` but received {expert_review_id!r}") + return await self._get( + f"/api/projects/{project_id}/expert_reviews/{expert_review_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertReviewRetrieveResponse, + ) + + def list( + self, + project_id: str, + *, + created_at_end: Union[str, datetime, None] | Omit = omit, + created_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_at_end: Union[str, datetime, None] | Omit = omit, + last_edited_at_start: Union[str, datetime, None] | Omit = omit, + last_edited_by: Optional[str] | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + review_status: Optional[List[Literal["good", "bad"]]] | Omit = omit, + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ExpertReviewListResponse, AsyncOffsetPageExpertReviews[ExpertReviewListResponse]]: + """ + List Expert Reviews Route + + Args: + created_at_end: Filter expert reviews created at or before this timestamp + + created_at_start: Filter expert reviews created at or after this timestamp + + last_edited_at_end: Filter expert reviews last edited at or before this timestamp + + last_edited_at_start: Filter expert reviews last edited at or after this timestamp + + last_edited_by: Filter expert reviews last edited by user ID + + order: Sort order + + review_status: Filter expert reviews by review status + + sort: Sort expert reviews by field + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get_api_list( + f"/api/projects/{project_id}/expert_reviews/", + page=AsyncOffsetPageExpertReviews[ExpertReviewListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "created_at_end": created_at_end, + "created_at_start": created_at_start, + "last_edited_at_end": last_edited_at_end, + "last_edited_at_start": last_edited_at_start, + "last_edited_by": last_edited_by, + "limit": limit, + "offset": offset, + "order": order, + "review_status": review_status, + "sort": sort, + }, + expert_review_list_params.ExpertReviewListParams, + ), + ), + model=ExpertReviewListResponse, + ) + + async def delete( + self, + expert_review_id: str, + *, + project_id: str, + original_query_log_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Expert Review Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_review_id: + raise ValueError(f"Expected a non-empty value for `expert_review_id` but received {expert_review_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/api/projects/{project_id}/expert_reviews/{expert_review_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"original_query_log_id": original_query_log_id}, + expert_review_delete_params.ExpertReviewDeleteParams, + ), + ), + cast_to=NoneType, + ) + + async def edit( + self, + expert_review_id: str, + *, + project_id: str, + explanation: Optional[str] | Omit = omit, + review_status: Optional[Literal["good", "bad"]] | 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, + ) -> ExpertReviewEditResponse: + """ + Update Expert Review Route + + 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 project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not expert_review_id: + raise ValueError(f"Expected a non-empty value for `expert_review_id` but received {expert_review_id!r}") + return await self._patch( + f"/api/projects/{project_id}/expert_reviews/{expert_review_id}", + body=await async_maybe_transform( + { + "explanation": explanation, + "review_status": review_status, + }, + expert_review_edit_params.ExpertReviewEditParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExpertReviewEditResponse, + ) + + +class ExpertReviewsResourceWithRawResponse: + def __init__(self, expert_reviews: ExpertReviewsResource) -> None: + self._expert_reviews = expert_reviews + + self.create = to_raw_response_wrapper( + expert_reviews.create, + ) + self.retrieve = to_raw_response_wrapper( + expert_reviews.retrieve, + ) + self.list = to_raw_response_wrapper( + expert_reviews.list, + ) + self.delete = to_raw_response_wrapper( + expert_reviews.delete, + ) + self.edit = to_raw_response_wrapper( + expert_reviews.edit, + ) + + +class AsyncExpertReviewsResourceWithRawResponse: + def __init__(self, expert_reviews: AsyncExpertReviewsResource) -> None: + self._expert_reviews = expert_reviews + + self.create = async_to_raw_response_wrapper( + expert_reviews.create, + ) + self.retrieve = async_to_raw_response_wrapper( + expert_reviews.retrieve, + ) + self.list = async_to_raw_response_wrapper( + expert_reviews.list, + ) + self.delete = async_to_raw_response_wrapper( + expert_reviews.delete, + ) + self.edit = async_to_raw_response_wrapper( + expert_reviews.edit, + ) + + +class ExpertReviewsResourceWithStreamingResponse: + def __init__(self, expert_reviews: ExpertReviewsResource) -> None: + self._expert_reviews = expert_reviews + + self.create = to_streamed_response_wrapper( + expert_reviews.create, + ) + self.retrieve = to_streamed_response_wrapper( + expert_reviews.retrieve, + ) + self.list = to_streamed_response_wrapper( + expert_reviews.list, + ) + self.delete = to_streamed_response_wrapper( + expert_reviews.delete, + ) + self.edit = to_streamed_response_wrapper( + expert_reviews.edit, + ) + + +class AsyncExpertReviewsResourceWithStreamingResponse: + def __init__(self, expert_reviews: AsyncExpertReviewsResource) -> None: + self._expert_reviews = expert_reviews + + self.create = async_to_streamed_response_wrapper( + expert_reviews.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + expert_reviews.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + expert_reviews.list, + ) + self.delete = async_to_streamed_response_wrapper( + expert_reviews.delete, + ) + self.edit = async_to_streamed_response_wrapper( + expert_reviews.edit, + ) diff --git a/src/codex/resources/projects/remediations/remediations.py b/src/codex/resources/projects/remediations/remediations.py index 23873afb..f02cba8b 100644 --- a/src/codex/resources/projects/remediations/remediations.py +++ b/src/codex/resources/projects/remediations/remediations.py @@ -28,6 +28,14 @@ ExpertAnswersResourceWithStreamingResponse, AsyncExpertAnswersResourceWithStreamingResponse, ) +from .expert_reviews import ( + ExpertReviewsResource, + AsyncExpertReviewsResource, + ExpertReviewsResourceWithRawResponse, + AsyncExpertReviewsResourceWithRawResponse, + ExpertReviewsResourceWithStreamingResponse, + AsyncExpertReviewsResourceWithStreamingResponse, +) from ...._base_client import AsyncPaginator, make_request_options from ....types.projects import ( remediation_list_params, @@ -54,6 +62,10 @@ class RemediationsResource(SyncAPIResource): def expert_answers(self) -> ExpertAnswersResource: return ExpertAnswersResource(self._client) + @cached_property + def expert_reviews(self) -> ExpertReviewsResource: + return ExpertReviewsResource(self._client) + @cached_property def with_raw_response(self) -> RemediationsResourceWithRawResponse: """ @@ -539,6 +551,10 @@ class AsyncRemediationsResource(AsyncAPIResource): def expert_answers(self) -> AsyncExpertAnswersResource: return AsyncExpertAnswersResource(self._client) + @cached_property + def expert_reviews(self) -> AsyncExpertReviewsResource: + return AsyncExpertReviewsResource(self._client) + @cached_property def with_raw_response(self) -> AsyncRemediationsResourceWithRawResponse: """ @@ -1085,6 +1101,10 @@ def __init__(self, remediations: RemediationsResource) -> None: def expert_answers(self) -> ExpertAnswersResourceWithRawResponse: return ExpertAnswersResourceWithRawResponse(self._remediations.expert_answers) + @cached_property + def expert_reviews(self) -> ExpertReviewsResourceWithRawResponse: + return ExpertReviewsResourceWithRawResponse(self._remediations.expert_reviews) + class AsyncRemediationsResourceWithRawResponse: def __init__(self, remediations: AsyncRemediationsResource) -> None: @@ -1150,6 +1170,10 @@ def __init__(self, remediations: AsyncRemediationsResource) -> None: def expert_answers(self) -> AsyncExpertAnswersResourceWithRawResponse: return AsyncExpertAnswersResourceWithRawResponse(self._remediations.expert_answers) + @cached_property + def expert_reviews(self) -> AsyncExpertReviewsResourceWithRawResponse: + return AsyncExpertReviewsResourceWithRawResponse(self._remediations.expert_reviews) + class RemediationsResourceWithStreamingResponse: def __init__(self, remediations: RemediationsResource) -> None: @@ -1215,6 +1239,10 @@ def __init__(self, remediations: RemediationsResource) -> None: def expert_answers(self) -> ExpertAnswersResourceWithStreamingResponse: return ExpertAnswersResourceWithStreamingResponse(self._remediations.expert_answers) + @cached_property + def expert_reviews(self) -> ExpertReviewsResourceWithStreamingResponse: + return ExpertReviewsResourceWithStreamingResponse(self._remediations.expert_reviews) + class AsyncRemediationsResourceWithStreamingResponse: def __init__(self, remediations: AsyncRemediationsResource) -> None: @@ -1279,3 +1307,7 @@ def __init__(self, remediations: AsyncRemediationsResource) -> None: @cached_property def expert_answers(self) -> AsyncExpertAnswersResourceWithStreamingResponse: return AsyncExpertAnswersResourceWithStreamingResponse(self._remediations.expert_answers) + + @cached_property + def expert_reviews(self) -> AsyncExpertReviewsResourceWithStreamingResponse: + return AsyncExpertReviewsResourceWithStreamingResponse(self._remediations.expert_reviews) diff --git a/src/codex/types/projects/remediations/__init__.py b/src/codex/types/projects/remediations/__init__.py index da142200..520c5974 100644 --- a/src/codex/types/projects/remediations/__init__.py +++ b/src/codex/types/projects/remediations/__init__.py @@ -3,13 +3,21 @@ from __future__ import annotations from .expert_answer_list_params import ExpertAnswerListParams as ExpertAnswerListParams +from .expert_review_edit_params import ExpertReviewEditParams as ExpertReviewEditParams +from .expert_review_list_params import ExpertReviewListParams as ExpertReviewListParams from .expert_answer_create_params import ExpertAnswerCreateParams as ExpertAnswerCreateParams from .expert_answer_list_response import ExpertAnswerListResponse as ExpertAnswerListResponse +from .expert_review_create_params import ExpertReviewCreateParams as ExpertReviewCreateParams +from .expert_review_delete_params import ExpertReviewDeleteParams as ExpertReviewDeleteParams +from .expert_review_edit_response import ExpertReviewEditResponse as ExpertReviewEditResponse +from .expert_review_list_response import ExpertReviewListResponse as ExpertReviewListResponse from .expert_answer_pause_response import ExpertAnswerPauseResponse as ExpertAnswerPauseResponse from .expert_answer_create_response import ExpertAnswerCreateResponse as ExpertAnswerCreateResponse +from .expert_review_create_response import ExpertReviewCreateResponse as ExpertReviewCreateResponse from .expert_answer_publish_response import ExpertAnswerPublishResponse as ExpertAnswerPublishResponse from .expert_answer_unpause_response import ExpertAnswerUnpauseResponse as ExpertAnswerUnpauseResponse from .expert_answer_retrieve_response import ExpertAnswerRetrieveResponse as ExpertAnswerRetrieveResponse +from .expert_review_retrieve_response import ExpertReviewRetrieveResponse as ExpertReviewRetrieveResponse from .expert_answer_edit_answer_params import ExpertAnswerEditAnswerParams as ExpertAnswerEditAnswerParams from .expert_answer_edit_answer_response import ExpertAnswerEditAnswerResponse as ExpertAnswerEditAnswerResponse from .expert_answer_edit_draft_answer_params import ( diff --git a/src/codex/types/projects/remediations/expert_review_create_params.py b/src/codex/types/projects/remediations/expert_review_create_params.py new file mode 100644 index 00000000..c871bd89 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_create_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ExpertReviewCreateParams"] + + +class ExpertReviewCreateParams(TypedDict, total=False): + original_query_log_id: Required[str] + """ID of the original query log""" + + review_status: Required[Literal["good", "bad"]] + """Expert review status - 'good' or 'bad'""" + + generate_guidance: bool + + explanation: Optional[str] + """Optional explanation for expert review""" diff --git a/src/codex/types/projects/remediations/expert_review_create_response.py b/src/codex/types/projects/remediations/expert_review_create_response.py new file mode 100644 index 00000000..154d8e04 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_create_response.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertReviewCreateResponse"] + + +class ExpertReviewCreateResponse(BaseModel): + id: str + + created_at: datetime + + last_edited_at: datetime + + last_edited_by: Optional[str] = None + + project_id: str + + review_status: Literal["good", "bad"] + """Expert review status - 'good' or 'bad'""" + + deleted_at: Optional[datetime] = None + + explanation: Optional[str] = None + """Optional explanation for expert review""" diff --git a/src/codex/types/projects/remediations/expert_review_delete_params.py b/src/codex/types/projects/remediations/expert_review_delete_params.py new file mode 100644 index 00000000..440bf07a --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_delete_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ExpertReviewDeleteParams"] + + +class ExpertReviewDeleteParams(TypedDict, total=False): + project_id: Required[str] + + original_query_log_id: Optional[str] diff --git a/src/codex/types/projects/remediations/expert_review_edit_params.py b/src/codex/types/projects/remediations/expert_review_edit_params.py new file mode 100644 index 00000000..d0b48b89 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_edit_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ExpertReviewEditParams"] + + +class ExpertReviewEditParams(TypedDict, total=False): + project_id: Required[str] + + explanation: Optional[str] + + review_status: Optional[Literal["good", "bad"]] diff --git a/src/codex/types/projects/remediations/expert_review_edit_response.py b/src/codex/types/projects/remediations/expert_review_edit_response.py new file mode 100644 index 00000000..1c3d845f --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_edit_response.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertReviewEditResponse"] + + +class ExpertReviewEditResponse(BaseModel): + id: str + + created_at: datetime + + last_edited_at: datetime + + last_edited_by: Optional[str] = None + + project_id: str + + review_status: Literal["good", "bad"] + """Expert review status - 'good' or 'bad'""" + + deleted_at: Optional[datetime] = None + + explanation: Optional[str] = None + """Optional explanation for expert review""" diff --git a/src/codex/types/projects/remediations/expert_review_list_params.py b/src/codex/types/projects/remediations/expert_review_list_params.py new file mode 100644 index 00000000..b316b2f5 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_list_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["ExpertReviewListParams"] + + +class ExpertReviewListParams(TypedDict, total=False): + created_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter expert reviews created at or before this timestamp""" + + created_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter expert reviews created at or after this timestamp""" + + last_edited_at_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter expert reviews last edited at or before this timestamp""" + + last_edited_at_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Filter expert reviews last edited at or after this timestamp""" + + last_edited_by: Optional[str] + """Filter expert reviews last edited by user ID""" + + limit: int + + offset: int + + order: Literal["asc", "desc"] + """Sort order""" + + review_status: Optional[List[Literal["good", "bad"]]] + """Filter expert reviews by review status""" + + sort: Optional[Literal["created_at", "last_edited_at", "resolved_logs_count"]] + """Sort expert reviews by field""" diff --git a/src/codex/types/projects/remediations/expert_review_list_response.py b/src/codex/types/projects/remediations/expert_review_list_response.py new file mode 100644 index 00000000..99d26aba --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_list_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertReviewListResponse"] + + +class ExpertReviewListResponse(BaseModel): + id: str + + created_at: datetime + + evaluated_response: Optional[str] = None + + last_edited_at: datetime + + last_edited_by: Optional[str] = None + + project_id: str + + query: str + + resolved_logs_count: int + + review_status: Literal["good", "bad"] + """Expert review status - 'good' or 'bad'""" + + deleted_at: Optional[datetime] = None + + explanation: Optional[str] = None + """Optional explanation for expert review""" diff --git a/src/codex/types/projects/remediations/expert_review_retrieve_response.py b/src/codex/types/projects/remediations/expert_review_retrieve_response.py new file mode 100644 index 00000000..9cb0da69 --- /dev/null +++ b/src/codex/types/projects/remediations/expert_review_retrieve_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExpertReviewRetrieveResponse"] + + +class ExpertReviewRetrieveResponse(BaseModel): + id: str + + created_at: datetime + + evaluated_response: Optional[str] = None + + last_edited_at: datetime + + last_edited_by: Optional[str] = None + + project_id: str + + query: str + + resolved_logs_count: int + + review_status: Literal["good", "bad"] + """Expert review status - 'good' or 'bad'""" + + deleted_at: Optional[datetime] = None + + explanation: Optional[str] = None + """Optional explanation for expert review""" diff --git a/tests/api_resources/projects/remediations/test_expert_reviews.py b/tests/api_resources/projects/remediations/test_expert_reviews.py new file mode 100644 index 00000000..f65b0fb5 --- /dev/null +++ b/tests/api_resources/projects/remediations/test_expert_reviews.py @@ -0,0 +1,631 @@ +# 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 codex import Codex, AsyncCodex +from tests.utils import assert_matches_type +from codex._utils import parse_datetime +from codex.pagination import SyncOffsetPageExpertReviews, AsyncOffsetPageExpertReviews +from codex.types.projects.remediations import ( + ExpertReviewEditResponse, + ExpertReviewListResponse, + ExpertReviewCreateResponse, + ExpertReviewRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExpertReviews: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + generate_guidance=True, + explanation="explanation", + ) + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Codex) -> None: + response = client.projects.remediations.expert_reviews.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = response.parse() + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Codex) -> None: + with client.projects.remediations.expert_reviews.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = response.parse() + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_create(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.create( + project_id="", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertReviewRetrieveResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Codex) -> None: + response = client.projects.remediations.expert_reviews.with_raw_response.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = response.parse() + assert_matches_type(ExpertReviewRetrieveResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Codex) -> None: + with client.projects.remediations.expert_reviews.with_streaming_response.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = response.parse() + assert_matches_type(ExpertReviewRetrieveResponse, expert_review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_review_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.retrieve( + expert_review_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + review_status=["good"], + sort="created_at", + ) + assert_matches_type(SyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Codex) -> None: + response = client.projects.remediations.expert_reviews.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = response.parse() + assert_matches_type(SyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Codex) -> None: + with client.projects.remediations.expert_reviews.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = response.parse() + assert_matches_type(SyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_list(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert expert_review is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete_with_all_params(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert expert_review is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Codex) -> None: + response = client.projects.remediations.expert_reviews.with_raw_response.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = response.parse() + assert expert_review is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Codex) -> None: + with client.projects.remediations.expert_reviews.with_streaming_response.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = response.parse() + assert expert_review is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_review_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.delete( + expert_review_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_edit(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_edit_with_all_params(self, client: Codex) -> None: + expert_review = client.projects.remediations.expert_reviews.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + explanation="explanation", + review_status="good", + ) + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_edit(self, client: Codex) -> None: + response = client.projects.remediations.expert_reviews.with_raw_response.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = response.parse() + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_edit(self, client: Codex) -> None: + with client.projects.remediations.expert_reviews.with_streaming_response.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = response.parse() + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_edit(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_review_id` but received ''"): + client.projects.remediations.expert_reviews.with_raw_response.edit( + expert_review_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncExpertReviews: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + generate_guidance=True, + explanation="explanation", + ) + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_reviews.with_raw_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = await response.parse() + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_reviews.with_streaming_response.create( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = await response.parse() + assert_matches_type(ExpertReviewCreateResponse, expert_review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_create(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.create( + project_id="", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + review_status="good", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertReviewRetrieveResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_reviews.with_raw_response.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = await response.parse() + assert_matches_type(ExpertReviewRetrieveResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_reviews.with_streaming_response.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = await response.parse() + assert_matches_type(ExpertReviewRetrieveResponse, expert_review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.retrieve( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_review_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.retrieve( + expert_review_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + created_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + created_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_end=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_at_start=parse_datetime("2019-12-27T18:11:19.117Z"), + last_edited_by="last_edited_by", + limit=1, + offset=0, + order="asc", + review_status=["good"], + sort="created_at", + ) + assert_matches_type(AsyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_reviews.with_raw_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = await response.parse() + assert_matches_type(AsyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_reviews.with_streaming_response.list( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = await response.parse() + assert_matches_type( + AsyncOffsetPageExpertReviews[ExpertReviewListResponse], expert_review, path=["response"] + ) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_list(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.list( + project_id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert expert_review is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete_with_all_params(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + original_query_log_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert expert_review is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_reviews.with_raw_response.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = await response.parse() + assert expert_review is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_reviews.with_streaming_response.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = await response.parse() + assert expert_review is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.delete( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_review_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.delete( + expert_review_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_edit(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_edit_with_all_params(self, async_client: AsyncCodex) -> None: + expert_review = await async_client.projects.remediations.expert_reviews.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + explanation="explanation", + review_status="good", + ) + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_edit(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.remediations.expert_reviews.with_raw_response.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + expert_review = await response.parse() + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_edit(self, async_client: AsyncCodex) -> None: + async with async_client.projects.remediations.expert_reviews.with_streaming_response.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + expert_review = await response.parse() + assert_matches_type(ExpertReviewEditResponse, expert_review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_edit(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.edit( + expert_review_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `expert_review_id` but received ''"): + await async_client.projects.remediations.expert_reviews.with_raw_response.edit( + expert_review_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) From a08794314f9a411e54e53c49f533124b1269dd1c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:09:45 +0000 Subject: [PATCH 300/320] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/codex/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2ce25fec..36b2affb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.32" + ".": "0.1.0-alpha.34" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 5aaf2dfe..bcfd202b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.32" +version = "0.1.0-alpha.34" description = "The official Python library for the Codex API" dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index 57e8ea7c..c2ea81e9 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.32" # x-release-please-version +__version__ = "0.1.0-alpha.34" # x-release-please-version From 52ad1e77f935cc0b065a855ab9d9619e120d93e7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:17:34 +0000 Subject: [PATCH 301/320] feat(api): api update --- .stats.yml | 2 +- .../types/projects/query_log_list_by_group_response.py | 6 ------ .../types/projects/query_log_list_groups_response.py | 6 ------ src/codex/types/projects/query_log_list_response.py | 6 ------ .../types/projects/query_log_retrieve_response.py | 10 ++++------ .../remediation_list_resolved_logs_response.py | 6 ------ .../remediations/expert_review_list_response.py | 2 ++ .../remediations/expert_review_retrieve_response.py | 2 ++ 8 files changed, 9 insertions(+), 31 deletions(-) diff --git a/.stats.yml b/.stats.yml index c021c17a..5104bd7e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 70 -openapi_spec_hash: 9018ebfb2a9e1afa87058b3a4bd41b0b +openapi_spec_hash: 3e4a110bf3d3aca12173e6023e09f606 config_hash: aad16f20fed13ac50211fc1d0e2ea621 diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 1a89baa8..e0c732b4 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -467,12 +467,6 @@ class QueryLogsByGroupQueryLog(BaseModel): expert review. Expert review will override the original guardrail decision. """ - expert_override_log_id: Optional[str] = None - """ - ID of the query log with expert review that overrode the original guardrail - decision. - """ - expert_review_created_at: Optional[datetime] = None """When the expert review was created""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index fe70223b..fd8edad1 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -462,12 +462,6 @@ class QueryLogListGroupsResponse(BaseModel): expert review. Expert review will override the original guardrail decision. """ - expert_override_log_id: Optional[str] = None - """ - ID of the query log with expert review that overrode the original guardrail - decision. - """ - expert_review_created_at: Optional[datetime] = None """When the expert review was created""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index dc7768f7..f5d70708 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -450,12 +450,6 @@ class QueryLogListResponse(BaseModel): expert review. Expert review will override the original guardrail decision. """ - expert_override_log_id: Optional[str] = None - """ - ID of the query log with expert review that overrode the original guardrail - decision. - """ - expert_review_created_at: Optional[datetime] = None """When the expert review was created""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index db919432..69388b97 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -367,6 +367,8 @@ class QueryLogRetrieveResponse(BaseModel): expert_answer_id: Optional[str] = None + expert_override_log_id: Optional[str] = None + formatted_escalation_eval_scores: Optional[Dict[str, FormattedEscalationEvalScores]] = None formatted_eval_scores: Optional[Dict[str, FormattedEvalScores]] = None @@ -392,6 +394,8 @@ class QueryLogRetrieveResponse(BaseModel): issue_status: Literal["addressed", "unaddressed"] """Manual review status override for remediations.""" + log_needs_review: bool + needs_review: bool project_id: str @@ -457,12 +461,6 @@ class QueryLogRetrieveResponse(BaseModel): expert review. Expert review will override the original guardrail decision. """ - expert_override_log_id: Optional[str] = None - """ - ID of the query log with expert review that overrode the original guardrail - decision. - """ - expert_review_created_at: Optional[datetime] = None """When the expert review was created""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 9f1b77b4..eef33f86 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -457,12 +457,6 @@ class QueryLog(BaseModel): expert review. Expert review will override the original guardrail decision. """ - expert_override_log_id: Optional[str] = None - """ - ID of the query log with expert review that overrode the original guardrail - decision. - """ - expert_review_created_at: Optional[datetime] = None """When the expert review was created""" diff --git a/src/codex/types/projects/remediations/expert_review_list_response.py b/src/codex/types/projects/remediations/expert_review_list_response.py index 99d26aba..eadb9743 100644 --- a/src/codex/types/projects/remediations/expert_review_list_response.py +++ b/src/codex/types/projects/remediations/expert_review_list_response.py @@ -16,6 +16,8 @@ class ExpertReviewListResponse(BaseModel): evaluated_response: Optional[str] = None + expert_override_log_id: str + last_edited_at: datetime last_edited_by: Optional[str] = None diff --git a/src/codex/types/projects/remediations/expert_review_retrieve_response.py b/src/codex/types/projects/remediations/expert_review_retrieve_response.py index 9cb0da69..f48fac2f 100644 --- a/src/codex/types/projects/remediations/expert_review_retrieve_response.py +++ b/src/codex/types/projects/remediations/expert_review_retrieve_response.py @@ -16,6 +16,8 @@ class ExpertReviewRetrieveResponse(BaseModel): evaluated_response: Optional[str] = None + expert_override_log_id: str + last_edited_at: datetime last_edited_by: Optional[str] = None From 531e03a247a3678a71a501a7c919b1f620b5e12d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 03:23:13 +0000 Subject: [PATCH 302/320] chore: add Python 3.14 classifier and testing --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index bcfd202b..89d6235a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "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", From 12ac10523c11776356e0f8d9f05e234455fdda68 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:17:49 +0000 Subject: [PATCH 303/320] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 5104bd7e..220b7dea 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 70 -openapi_spec_hash: 3e4a110bf3d3aca12173e6023e09f606 +openapi_spec_hash: 022a28b575651c2f21f43b1148141ce3 config_hash: aad16f20fed13ac50211fc1d0e2ea621 From 3654231f24dbb605c5cb10cb77278959054d11a5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:17:48 +0000 Subject: [PATCH 304/320] feat(api): api update --- .stats.yml | 2 +- .../types/projects/query_log_list_by_group_response.py | 9 +++++++++ .../types/projects/query_log_list_groups_response.py | 9 +++++++++ src/codex/types/projects/query_log_list_response.py | 9 +++++++++ src/codex/types/projects/query_log_retrieve_response.py | 9 +++++++++ .../projects/remediation_list_resolved_logs_response.py | 9 +++++++++ 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 220b7dea..7a6c349f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 70 -openapi_spec_hash: 022a28b575651c2f21f43b1148141ce3 +openapi_spec_hash: 11279400677011ad5dc1ebba33216ae4 config_hash: aad16f20fed13ac50211fc1d0e2ea621 diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index e0c732b4..34b722b4 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -523,6 +523,15 @@ class QueryLogsByGroupQueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + system_prompt: Optional[str] = None + """ + Content of the first system message associated with this query log, if + available. + """ + + system_prompt_hash: Optional[str] = None + """SHA-256 hash of the system prompt content for quick equality checks.""" + tools: Optional[List[QueryLogsByGroupQueryLogTool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index fd8edad1..1587af7f 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -518,6 +518,15 @@ class QueryLogListGroupsResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + system_prompt: Optional[str] = None + """ + Content of the first system message associated with this query log, if + available. + """ + + system_prompt_hash: Optional[str] = None + """SHA-256 hash of the system prompt content for quick equality checks.""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index f5d70708..e71f05b4 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -503,6 +503,15 @@ class QueryLogListResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + system_prompt: Optional[str] = None + """ + Content of the first system message associated with this query log, if + available. + """ + + system_prompt_hash: Optional[str] = None + """SHA-256 hash of the system prompt content for quick equality checks.""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 69388b97..61168404 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -517,6 +517,15 @@ class QueryLogRetrieveResponse(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + system_prompt: Optional[str] = None + """ + Content of the first system message associated with this query log, if + available. + """ + + system_prompt_hash: Optional[str] = None + """SHA-256 hash of the system prompt content for quick equality checks.""" + tools: Optional[List[Tool]] = None """Tools to use for the LLM call. diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index eef33f86..986f898b 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -510,6 +510,15 @@ class QueryLog(BaseModel): primary_eval_issue_score: Optional[float] = None """Score of the primary eval issue""" + system_prompt: Optional[str] = None + """ + Content of the first system message associated with this query log, if + available. + """ + + system_prompt_hash: Optional[str] = None + """SHA-256 hash of the system prompt content for quick equality checks.""" + tools: Optional[List[QueryLogTool]] = None """Tools to use for the LLM call. From 8dc4b9e0e0b0db067d8f1e5508aa5be18eb6bdb8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 03:08:28 +0000 Subject: [PATCH 305/320] fix: ensure streams are always closed --- src/codex/_streaming.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/codex/_streaming.py b/src/codex/_streaming.py index d9c4a80a..e6c997ed 100644 --- a/src/codex/_streaming.py +++ b/src/codex/_streaming.py @@ -54,11 +54,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) - - # As we might not fully consume the response stream, we need to close it explicitly - response.close() + 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 @@ -117,11 +118,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) - - # As we might not fully consume the response stream, we need to close it explicitly - await response.aclose() + 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 From a9f53b0227c591b6a7ad242ea2631bafe00b2e6d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 03:09:32 +0000 Subject: [PATCH 306/320] chore(deps): mypy 1.18.1 has a regression, pin to 1.17 --- pyproject.toml | 2 +- requirements-dev.lock | 4 +++- requirements.lock | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 89d6235a..0f261560 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ managed = true # version pins are in requirements-dev.lock dev-dependencies = [ "pyright==1.1.399", - "mypy", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", diff --git a/requirements-dev.lock b/requirements-dev.lock index d728372e..1b3f5149 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -72,7 +72,7 @@ mdurl==0.1.2 multidict==6.4.4 # via aiohttp # via yarl -mypy==1.14.1 +mypy==1.17.0 mypy-extensions==1.0.0 # via mypy nodeenv==1.8.0 @@ -81,6 +81,8 @@ nox==2023.4.22 packaging==23.2 # via nox # via pytest +pathspec==0.12.1 + # via mypy platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 diff --git a/requirements.lock b/requirements.lock index 4b916da1..b6cc26c1 100644 --- a/requirements.lock +++ b/requirements.lock @@ -55,21 +55,21 @@ multidict==6.4.4 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.11.9 +pydantic==2.12.5 # via codex-sdk -pydantic-core==2.33.2 +pydantic-core==2.41.5 # via pydantic sniffio==1.3.0 # via anyio # via codex-sdk -typing-extensions==4.12.2 +typing-extensions==4.15.0 # via anyio # via codex-sdk # via multidict # via pydantic # via pydantic-core # via typing-inspection -typing-inspection==0.4.1 +typing-inspection==0.4.2 # via pydantic yarl==1.20.0 # via aiohttp From 6d4d44d5f9a78ab29ed97f622a71deaa3516dc44 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 03:49:49 +0000 Subject: [PATCH 307/320] chore: update lockfile --- pyproject.toml | 14 +++--- requirements-dev.lock | 108 +++++++++++++++++++++++------------------- requirements.lock | 31 ++++++------ 3 files changed, 83 insertions(+), 70 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0f261560..e58c104c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,14 +7,16 @@ license = "MIT" authors = [ { name = "Codex", email = "team@cleanlab.ai" }, ] + dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.10, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.10, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", ] + requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", diff --git a/requirements-dev.lock b/requirements-dev.lock index 1b3f5149..90dc04b1 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,40 +12,45 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.12.8 +aiohttp==3.13.2 # via codex-sdk # via httpx-aiohttp -aiosignal==1.3.2 +aiosignal==1.4.0 # via aiohttp -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.0 # via codex-sdk # via httpx -argcomplete==3.1.2 +argcomplete==3.6.3 # via nox async-timeout==5.0.1 # via aiohttp -attrs==25.3.0 +attrs==25.4.0 # via aiohttp -certifi==2023.7.22 + # via nox +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +certifi==2025.11.12 # 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 codex-sdk -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio # via pytest -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -filelock==3.12.4 +filelock==3.19.1 # via virtualenv -frozenlist==1.6.2 +frozenlist==1.8.0 # via aiohttp # via aiosignal h11==0.16.0 @@ -58,82 +63,87 @@ httpx==0.28.1 # via respx httpx-aiohttp==0.1.9 # via codex-sdk -idna==3.4 +humanize==4.13.0 + # via nox +idna==3.11 # via anyio # via httpx # via yarl -importlib-metadata==7.0.0 -iniconfig==2.0.0 +importlib-metadata==8.7.0 +iniconfig==2.1.0 # via pytest markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -multidict==6.4.4 +multidict==6.7.0 # via aiohttp # via yarl mypy==1.17.0 -mypy-extensions==1.0.0 +mypy-extensions==1.1.0 # via mypy -nodeenv==1.8.0 +nodeenv==1.9.1 # via pyright -nox==2023.4.22 -packaging==23.2 +nox==2025.11.12 +packaging==25.0 + # via dependency-groups # via nox # via pytest pathspec==0.12.1 # via mypy -platformdirs==3.11.0 +platformdirs==4.4.0 # via virtualenv -pluggy==1.5.0 +pluggy==1.6.0 # via pytest -propcache==0.3.1 +propcache==0.4.1 # via aiohttp # via yarl -pydantic==2.11.9 +pydantic==2.12.5 # via codex-sdk -pydantic-core==2.33.2 +pydantic-core==2.41.5 # via pydantic -pygments==2.18.0 +pygments==2.19.2 + # via pytest # via rich pyright==1.1.399 -pytest==8.3.3 +pytest==8.4.2 # via pytest-asyncio # via pytest-xdist -pytest-asyncio==0.24.0 -pytest-xdist==3.7.0 -python-dateutil==2.8.2 +pytest-asyncio==1.2.0 +pytest-xdist==3.8.0 +python-dateutil==2.9.0.post0 # via time-machine -pytz==2023.3.post1 - # via dirty-equals respx==0.22.0 -rich==13.7.1 -ruff==0.9.4 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 +rich==14.2.0 +ruff==0.14.7 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via codex-sdk -time-machine==2.9.0 -tomli==2.0.2 +time-machine==2.19.0 +tomli==2.3.0 + # via dependency-groups # via mypy + # via nox # via pytest -typing-extensions==4.12.2 +typing-extensions==4.15.0 + # via aiosignal # via anyio # via codex-sdk + # via exceptiongroup # via multidict # via mypy # via pydantic # via pydantic-core # via pyright + # via pytest-asyncio # via typing-inspection -typing-inspection==0.4.1 + # via virtualenv +typing-inspection==0.4.2 # via pydantic -virtualenv==20.24.5 +virtualenv==20.35.4 # via nox -yarl==1.20.0 +yarl==1.22.0 # via aiohttp -zipp==3.17.0 +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index b6cc26c1..2cedc497 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,28 +12,28 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.12.8 +aiohttp==3.13.2 # via codex-sdk # via httpx-aiohttp -aiosignal==1.3.2 +aiosignal==1.4.0 # via aiohttp -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.0 # via codex-sdk # via httpx async-timeout==5.0.1 # via aiohttp -attrs==25.3.0 +attrs==25.4.0 # via aiohttp -certifi==2023.7.22 +certifi==2025.11.12 # via httpcore # via httpx -distro==1.8.0 +distro==1.9.0 # via codex-sdk -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio -frozenlist==1.6.2 +frozenlist==1.8.0 # via aiohttp # via aiosignal h11==0.16.0 @@ -45,31 +45,32 @@ httpx==0.28.1 # via httpx-aiohttp httpx-aiohttp==0.1.9 # via codex-sdk -idna==3.4 +idna==3.11 # via anyio # via httpx # via yarl -multidict==6.4.4 +multidict==6.7.0 # via aiohttp # via yarl -propcache==0.3.1 +propcache==0.4.1 # via aiohttp # via yarl pydantic==2.12.5 # via codex-sdk pydantic-core==2.41.5 # via pydantic -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via codex-sdk typing-extensions==4.15.0 + # via aiosignal # via anyio # via codex-sdk + # via exceptiongroup # via multidict # via pydantic # via pydantic-core # via typing-inspection typing-inspection==0.4.2 # via pydantic -yarl==1.20.0 +yarl==1.22.0 # via aiohttp From bdcaf29172a9ad826c5e81bbdd9434e09c37c3ac Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:18:15 +0000 Subject: [PATCH 308/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 60 +++++++++++++++++++ src/codex/types/project_retrieve_response.py | 13 +++- .../query_log_list_by_group_params.py | 12 ++++ .../query_log_list_by_group_response.py | 21 +++++++ .../projects/query_log_list_groups_params.py | 12 ++++ .../query_log_list_groups_response.py | 9 +++ .../types/projects/query_log_list_params.py | 12 ++++ .../types/projects/query_log_list_response.py | 9 +++ .../projects/query_log_retrieve_response.py | 9 +++ ...remediation_list_resolved_logs_response.py | 9 +++ .../api_resources/projects/test_query_logs.py | 12 ++++ 12 files changed, 178 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7a6c349f..3c7f25df 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 70 -openapi_spec_hash: 11279400677011ad5dc1ebba33216ae4 +openapi_spec_hash: 97ec07f3ab237f61ed0bbc359486cc0e config_hash: aad16f20fed13ac50211fc1d0e2ea621 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index fec11389..d88656fc 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -112,6 +112,7 @@ def list( guardrailed: Optional[bool] | Omit = omit, has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -122,6 +123,7 @@ def list( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -148,6 +150,9 @@ def list( has_tool_calls: Filter by whether the query log has tool calls + non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -168,6 +173,9 @@ def list( tool_call_names: Filter by names of tools called in the assistant response + triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -198,6 +206,7 @@ def list( "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, "limit": limit, + "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -205,6 +214,7 @@ def list( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, + "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_params.QueryLogListParams, @@ -267,6 +277,7 @@ def list_by_group( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -278,6 +289,7 @@ def list_by_group( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -306,6 +318,9 @@ def list_by_group( needs_review: Filter logs that need review + non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -328,6 +343,9 @@ def list_by_group( tool_call_names: Filter by names of tools called in the assistant response + triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -358,6 +376,7 @@ def list_by_group( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, + "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -366,6 +385,7 @@ def list_by_group( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, + "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_by_group_params.QueryLogListByGroupParams, @@ -388,6 +408,7 @@ def list_groups( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -398,6 +419,7 @@ def list_groups( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -426,6 +448,9 @@ def list_groups( needs_review: Filter log groups that need review + non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -447,6 +472,9 @@ def list_groups( tool_call_names: Filter by names of tools called in the assistant response + triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -478,6 +506,7 @@ def list_groups( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, + "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -485,6 +514,7 @@ def list_groups( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, + "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_groups_params.QueryLogListGroupsParams, @@ -637,6 +667,7 @@ def list( guardrailed: Optional[bool] | Omit = omit, has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -647,6 +678,7 @@ def list( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -673,6 +705,9 @@ def list( has_tool_calls: Filter by whether the query log has tool calls + non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -693,6 +728,9 @@ def list( tool_call_names: Filter by names of tools called in the assistant response + triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -723,6 +761,7 @@ def list( "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, "limit": limit, + "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -730,6 +769,7 @@ def list( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, + "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_params.QueryLogListParams, @@ -794,6 +834,7 @@ async def list_by_group( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -805,6 +846,7 @@ async def list_by_group( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -833,6 +875,9 @@ async def list_by_group( needs_review: Filter logs that need review + non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -855,6 +900,9 @@ async def list_by_group( tool_call_names: Filter by names of tools called in the assistant response + triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -885,6 +933,7 @@ async def list_by_group( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, + "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -893,6 +942,7 @@ async def list_by_group( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, + "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_by_group_params.QueryLogListByGroupParams, @@ -915,6 +965,7 @@ def list_groups( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -925,6 +976,7 @@ def list_groups( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -953,6 +1005,9 @@ def list_groups( needs_review: Filter log groups that need review + non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -974,6 +1029,9 @@ def list_groups( tool_call_names: Filter by names of tools called in the assistant response + triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -1005,6 +1063,7 @@ def list_groups( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, + "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -1012,6 +1071,7 @@ def list_groups( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, + "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_groups_params.QueryLogListGroupsParams, diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 8fe77415..75ff7414 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Dict, List, Optional from datetime import datetime from typing_extensions import Literal @@ -9,6 +9,7 @@ __all__ = [ "ProjectRetrieveResponse", "Config", + "ConfigDeterministicEval", "ConfigEvalConfig", "ConfigEvalConfigCustomEvals", "ConfigEvalConfigCustomEvalsEvals", @@ -27,6 +28,14 @@ ] +class ConfigDeterministicEval(BaseModel): + id: str + + name: str + + should_guardrail: bool + + class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): message: str """ @@ -469,6 +478,8 @@ class ConfigEvalConfig(BaseModel): class Config(BaseModel): + deterministic_evals: List[ConfigDeterministicEval] + ai_guidance_threshold: Optional[float] = None clustering_use_llm_matching: Optional[bool] = None diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 17a260bb..864e9639 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -39,6 +39,12 @@ class QueryLogListByGroupParams(TypedDict, total=False): needs_review: Optional[bool] """Filter logs that need review""" + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] + """ + Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + """ + offset: int order: Literal["asc", "desc"] @@ -76,5 +82,11 @@ class QueryLogListByGroupParams(TypedDict, total=False): tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] + """ + Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + """ + was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 34b722b4..a4403f3a 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -504,6 +504,12 @@ class QueryLogsByGroupQueryLog(BaseModel): itself. """ + non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """ + UUIDs of deterministic guardrails that were checked but not triggered for this + query + """ + original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -538,6 +544,9 @@ class QueryLogsByGroupQueryLog(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ + triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """UUIDs of deterministic guardrails that were triggered for this query""" + class QueryLogsByGroup(BaseModel): query_logs: List[QueryLogsByGroupQueryLog] @@ -572,6 +581,12 @@ class Filters(BaseModel): needs_review: Optional[bool] = None """Filter logs that need review""" + non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """ + Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + """ + passed_evals: Optional[List[str]] = None """Filter by evals that passed""" @@ -589,6 +604,12 @@ class Filters(BaseModel): tool_call_names: Optional[List[str]] = None """Filter by names of tools called in the assistant response""" + triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """ + Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + """ + was_cache_hit: Optional[bool] = None """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index ece65b16..118fbc62 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -39,6 +39,12 @@ class QueryLogListGroupsParams(TypedDict, total=False): needs_review: Optional[bool] """Filter log groups that need review""" + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] + """ + Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + """ + offset: int order: Literal["asc", "desc"] @@ -74,5 +80,11 @@ class QueryLogListGroupsParams(TypedDict, total=False): tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] + """ + Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + """ + was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 1587af7f..8d3c412b 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -499,6 +499,12 @@ class QueryLogListGroupsResponse(BaseModel): itself. """ + non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """ + UUIDs of deterministic guardrails that were checked but not triggered for this + query + """ + original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -532,3 +538,6 @@ class QueryLogListGroupsResponse(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ + + triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """UUIDs of deterministic guardrails that were triggered for this query""" diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index eb7858a6..86ed0e03 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -36,6 +36,12 @@ class QueryLogListParams(TypedDict, total=False): limit: int + non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] + """ + Filter logs where ANY of these deterministic guardrail IDs were checked but not + triggered (OR operation) + """ + offset: int order: Literal["asc", "desc"] @@ -70,5 +76,11 @@ class QueryLogListParams(TypedDict, total=False): tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" + triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] + """ + Filter logs where ANY of these deterministic guardrail IDs were triggered (OR + operation) + """ + was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index e71f05b4..e94cc728 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -484,6 +484,12 @@ class QueryLogListResponse(BaseModel): itself. """ + non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """ + UUIDs of deterministic guardrails that were checked but not triggered for this + query + """ + original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -517,3 +523,6 @@ class QueryLogListResponse(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ + + triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """UUIDs of deterministic guardrails that were triggered for this query""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 61168404..13baf788 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -498,6 +498,12 @@ class QueryLogRetrieveResponse(BaseModel): itself. """ + non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """ + UUIDs of deterministic guardrails that were checked but not triggered for this + query + """ + original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -531,3 +537,6 @@ class QueryLogRetrieveResponse(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ + + triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """UUIDs of deterministic guardrails that were triggered for this query""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 986f898b..25646599 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -491,6 +491,12 @@ class QueryLog(BaseModel): itself. """ + non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """ + UUIDs of deterministic guardrails that were checked but not triggered for this + query + """ + original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -525,6 +531,9 @@ class QueryLog(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ + triggered_deterministic_guardrail_ids: Optional[List[str]] = None + """UUIDs of deterministic guardrails that were triggered for this query""" + class RemediationListResolvedLogsResponse(BaseModel): query_logs: List[QueryLog] diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index e98cb278..420cb9c6 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -107,6 +107,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: guardrailed=True, has_tool_calls=True, limit=1, + non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -114,6 +115,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: search_text="search_text", sort="created_at", tool_call_names=["string"], + triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @@ -234,6 +236,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: has_tool_calls=True, limit=1, needs_review=True, + non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -242,6 +245,7 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: search_text="search_text", sort="created_at", tool_call_names=["string"], + triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) @@ -309,6 +313,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: has_tool_calls=True, limit=1, needs_review=True, + non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -316,6 +321,7 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: search_text="search_text", sort="created_at", tool_call_names=["string"], + triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) @@ -553,6 +559,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No guardrailed=True, has_tool_calls=True, limit=1, + non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -560,6 +567,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No search_text="search_text", sort="created_at", tool_call_names=["string"], + triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @@ -680,6 +688,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod has_tool_calls=True, limit=1, needs_review=True, + non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -688,6 +697,7 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod search_text="search_text", sort="created_at", tool_call_names=["string"], + triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) @@ -755,6 +765,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex has_tool_calls=True, limit=1, needs_review=True, + non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -762,6 +773,7 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex search_text="search_text", sort="created_at", tool_call_names=["string"], + triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) From 711664380bc1cf796f81e126e37372374754fbfe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 21:17:33 +0000 Subject: [PATCH 309/320] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/query_logs.py | 60 ------------------- src/codex/types/project_retrieve_response.py | 13 +--- .../query_log_list_by_group_params.py | 12 ---- .../query_log_list_by_group_response.py | 21 ------- .../projects/query_log_list_groups_params.py | 12 ---- .../query_log_list_groups_response.py | 9 --- .../types/projects/query_log_list_params.py | 12 ---- .../types/projects/query_log_list_response.py | 9 --- .../projects/query_log_retrieve_response.py | 9 --- ...remediation_list_resolved_logs_response.py | 9 --- .../api_resources/projects/test_query_logs.py | 12 ---- 12 files changed, 2 insertions(+), 178 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3c7f25df..7a6c349f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 70 -openapi_spec_hash: 97ec07f3ab237f61ed0bbc359486cc0e +openapi_spec_hash: 11279400677011ad5dc1ebba33216ae4 config_hash: aad16f20fed13ac50211fc1d0e2ea621 diff --git a/src/codex/resources/projects/query_logs.py b/src/codex/resources/projects/query_logs.py index d88656fc..fec11389 100644 --- a/src/codex/resources/projects/query_logs.py +++ b/src/codex/resources/projects/query_logs.py @@ -112,7 +112,6 @@ def list( guardrailed: Optional[bool] | Omit = omit, has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -123,7 +122,6 @@ def list( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -150,9 +148,6 @@ def list( has_tool_calls: Filter by whether the query log has tool calls - non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -173,9 +168,6 @@ def list( tool_call_names: Filter by names of tools called in the assistant response - triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -206,7 +198,6 @@ def list( "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, "limit": limit, - "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -214,7 +205,6 @@ def list( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, - "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_params.QueryLogListParams, @@ -277,7 +267,6 @@ def list_by_group( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -289,7 +278,6 @@ def list_by_group( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -318,9 +306,6 @@ def list_by_group( needs_review: Filter logs that need review - non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -343,9 +328,6 @@ def list_by_group( tool_call_names: Filter by names of tools called in the assistant response - triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -376,7 +358,6 @@ def list_by_group( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, - "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -385,7 +366,6 @@ def list_by_group( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, - "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_by_group_params.QueryLogListByGroupParams, @@ -408,7 +388,6 @@ def list_groups( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -419,7 +398,6 @@ def list_groups( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -448,9 +426,6 @@ def list_groups( needs_review: Filter log groups that need review - non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -472,9 +447,6 @@ def list_groups( tool_call_names: Filter by names of tools called in the assistant response - triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -506,7 +478,6 @@ def list_groups( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, - "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -514,7 +485,6 @@ def list_groups( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, - "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_groups_params.QueryLogListGroupsParams, @@ -667,7 +637,6 @@ def list( guardrailed: Optional[bool] | Omit = omit, has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -678,7 +647,6 @@ def list( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -705,9 +673,6 @@ def list( has_tool_calls: Filter by whether the query log has tool calls - non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -728,9 +693,6 @@ def list( tool_call_names: Filter by names of tools called in the assistant response - triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -761,7 +723,6 @@ def list( "guardrailed": guardrailed, "has_tool_calls": has_tool_calls, "limit": limit, - "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -769,7 +730,6 @@ def list( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, - "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_params.QueryLogListParams, @@ -834,7 +794,6 @@ async def list_by_group( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -846,7 +805,6 @@ async def list_by_group( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -875,9 +833,6 @@ async def list_by_group( needs_review: Filter logs that need review - non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -900,9 +855,6 @@ async def list_by_group( tool_call_names: Filter by names of tools called in the assistant response - triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -933,7 +885,6 @@ async def list_by_group( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, - "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -942,7 +893,6 @@ async def list_by_group( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, - "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_by_group_params.QueryLogListByGroupParams, @@ -965,7 +915,6 @@ def list_groups( has_tool_calls: Optional[bool] | Omit = omit, limit: int | Omit = omit, needs_review: Optional[bool] | Omit = omit, - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, offset: int | Omit = omit, order: Literal["asc", "desc"] | Omit = omit, passed_evals: Optional[SequenceNotStr[str]] | Omit = omit, @@ -976,7 +925,6 @@ def list_groups( search_text: Optional[str] | Omit = omit, sort: Optional[str] | Omit = omit, tool_call_names: Optional[SequenceNotStr[str]] | Omit = omit, - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] | Omit = omit, was_cache_hit: Optional[bool] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1005,9 +953,6 @@ def list_groups( needs_review: Filter log groups that need review - non_triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - passed_evals: Filter by evals that passed primary_eval_issue: Filter logs that have ANY of these primary evaluation issues (OR operation) @@ -1029,9 +974,6 @@ def list_groups( tool_call_names: Filter by names of tools called in the assistant response - triggered_deterministic_guardrail_ids: Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - was_cache_hit: Filter by cache hit status extra_headers: Send extra headers @@ -1063,7 +1005,6 @@ def list_groups( "has_tool_calls": has_tool_calls, "limit": limit, "needs_review": needs_review, - "non_triggered_deterministic_guardrail_ids": non_triggered_deterministic_guardrail_ids, "offset": offset, "order": order, "passed_evals": passed_evals, @@ -1071,7 +1012,6 @@ def list_groups( "search_text": search_text, "sort": sort, "tool_call_names": tool_call_names, - "triggered_deterministic_guardrail_ids": triggered_deterministic_guardrail_ids, "was_cache_hit": was_cache_hit, }, query_log_list_groups_params.QueryLogListGroupsParams, diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 75ff7414..8fe77415 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import Dict, Optional from datetime import datetime from typing_extensions import Literal @@ -9,7 +9,6 @@ __all__ = [ "ProjectRetrieveResponse", "Config", - "ConfigDeterministicEval", "ConfigEvalConfig", "ConfigEvalConfigCustomEvals", "ConfigEvalConfigCustomEvalsEvals", @@ -28,14 +27,6 @@ ] -class ConfigDeterministicEval(BaseModel): - id: str - - name: str - - should_guardrail: bool - - class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): message: str """ @@ -478,8 +469,6 @@ class ConfigEvalConfig(BaseModel): class Config(BaseModel): - deterministic_evals: List[ConfigDeterministicEval] - ai_guidance_threshold: Optional[float] = None clustering_use_llm_matching: Optional[bool] = None diff --git a/src/codex/types/projects/query_log_list_by_group_params.py b/src/codex/types/projects/query_log_list_by_group_params.py index 864e9639..17a260bb 100644 --- a/src/codex/types/projects/query_log_list_by_group_params.py +++ b/src/codex/types/projects/query_log_list_by_group_params.py @@ -39,12 +39,6 @@ class QueryLogListByGroupParams(TypedDict, total=False): needs_review: Optional[bool] """Filter logs that need review""" - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] - """ - Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - """ - offset: int order: Literal["asc", "desc"] @@ -82,11 +76,5 @@ class QueryLogListByGroupParams(TypedDict, total=False): tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] - """ - Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - """ - was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index a4403f3a..34b722b4 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -504,12 +504,6 @@ class QueryLogsByGroupQueryLog(BaseModel): itself. """ - non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """ - UUIDs of deterministic guardrails that were checked but not triggered for this - query - """ - original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -544,9 +538,6 @@ class QueryLogsByGroupQueryLog(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ - triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """UUIDs of deterministic guardrails that were triggered for this query""" - class QueryLogsByGroup(BaseModel): query_logs: List[QueryLogsByGroupQueryLog] @@ -581,12 +572,6 @@ class Filters(BaseModel): needs_review: Optional[bool] = None """Filter logs that need review""" - non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """ - Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - """ - passed_evals: Optional[List[str]] = None """Filter by evals that passed""" @@ -604,12 +589,6 @@ class Filters(BaseModel): tool_call_names: Optional[List[str]] = None """Filter by names of tools called in the assistant response""" - triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """ - Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - """ - was_cache_hit: Optional[bool] = None """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_groups_params.py b/src/codex/types/projects/query_log_list_groups_params.py index 118fbc62..ece65b16 100644 --- a/src/codex/types/projects/query_log_list_groups_params.py +++ b/src/codex/types/projects/query_log_list_groups_params.py @@ -39,12 +39,6 @@ class QueryLogListGroupsParams(TypedDict, total=False): needs_review: Optional[bool] """Filter log groups that need review""" - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] - """ - Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - """ - offset: int order: Literal["asc", "desc"] @@ -80,11 +74,5 @@ class QueryLogListGroupsParams(TypedDict, total=False): tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] - """ - Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - """ - was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 8d3c412b..1587af7f 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -499,12 +499,6 @@ class QueryLogListGroupsResponse(BaseModel): itself. """ - non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """ - UUIDs of deterministic guardrails that were checked but not triggered for this - query - """ - original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -538,6 +532,3 @@ class QueryLogListGroupsResponse(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ - - triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """UUIDs of deterministic guardrails that were triggered for this query""" diff --git a/src/codex/types/projects/query_log_list_params.py b/src/codex/types/projects/query_log_list_params.py index 86ed0e03..eb7858a6 100644 --- a/src/codex/types/projects/query_log_list_params.py +++ b/src/codex/types/projects/query_log_list_params.py @@ -36,12 +36,6 @@ class QueryLogListParams(TypedDict, total=False): limit: int - non_triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] - """ - Filter logs where ANY of these deterministic guardrail IDs were checked but not - triggered (OR operation) - """ - offset: int order: Literal["asc", "desc"] @@ -76,11 +70,5 @@ class QueryLogListParams(TypedDict, total=False): tool_call_names: Optional[SequenceNotStr[str]] """Filter by names of tools called in the assistant response""" - triggered_deterministic_guardrail_ids: Optional[SequenceNotStr[str]] - """ - Filter logs where ANY of these deterministic guardrail IDs were triggered (OR - operation) - """ - was_cache_hit: Optional[bool] """Filter by cache hit status""" diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index e94cc728..e71f05b4 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -484,12 +484,6 @@ class QueryLogListResponse(BaseModel): itself. """ - non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """ - UUIDs of deterministic guardrails that were checked but not triggered for this - query - """ - original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -523,6 +517,3 @@ class QueryLogListResponse(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ - - triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """UUIDs of deterministic guardrails that were triggered for this query""" diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 13baf788..61168404 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -498,12 +498,6 @@ class QueryLogRetrieveResponse(BaseModel): itself. """ - non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """ - UUIDs of deterministic guardrails that were checked but not triggered for this - query - """ - original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -537,6 +531,3 @@ class QueryLogRetrieveResponse(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ - - triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """UUIDs of deterministic guardrails that were triggered for this query""" diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 25646599..986f898b 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -491,12 +491,6 @@ class QueryLog(BaseModel): itself. """ - non_triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """ - UUIDs of deterministic guardrails that were checked but not triggered for this - query - """ - original_assistant_response: Optional[str] = None """The original assistant response that would have been displayed to the user. @@ -531,9 +525,6 @@ class QueryLog(BaseModel): If not provided, it is assumed no tools were provided to the LLM. """ - triggered_deterministic_guardrail_ids: Optional[List[str]] = None - """UUIDs of deterministic guardrails that were triggered for this query""" - class RemediationListResolvedLogsResponse(BaseModel): query_logs: List[QueryLog] diff --git a/tests/api_resources/projects/test_query_logs.py b/tests/api_resources/projects/test_query_logs.py index 420cb9c6..e98cb278 100644 --- a/tests/api_resources/projects/test_query_logs.py +++ b/tests/api_resources/projects/test_query_logs.py @@ -107,7 +107,6 @@ def test_method_list_with_all_params(self, client: Codex) -> None: guardrailed=True, has_tool_calls=True, limit=1, - non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -115,7 +114,6 @@ def test_method_list_with_all_params(self, client: Codex) -> None: search_text="search_text", sort="created_at", tool_call_names=["string"], - triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) assert_matches_type(SyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @@ -236,7 +234,6 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: has_tool_calls=True, limit=1, needs_review=True, - non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -245,7 +242,6 @@ def test_method_list_by_group_with_all_params(self, client: Codex) -> None: search_text="search_text", sort="created_at", tool_call_names=["string"], - triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) @@ -313,7 +309,6 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: has_tool_calls=True, limit=1, needs_review=True, - non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -321,7 +316,6 @@ def test_method_list_groups_with_all_params(self, client: Codex) -> None: search_text="search_text", sort="created_at", tool_call_names=["string"], - triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) @@ -559,7 +553,6 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No guardrailed=True, has_tool_calls=True, limit=1, - non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -567,7 +560,6 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No search_text="search_text", sort="created_at", tool_call_names=["string"], - triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) assert_matches_type(AsyncOffsetPageQueryLogs[QueryLogListResponse], query_log, path=["response"]) @@ -688,7 +680,6 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod has_tool_calls=True, limit=1, needs_review=True, - non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -697,7 +688,6 @@ async def test_method_list_by_group_with_all_params(self, async_client: AsyncCod search_text="search_text", sort="created_at", tool_call_names=["string"], - triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) @@ -765,7 +755,6 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex has_tool_calls=True, limit=1, needs_review=True, - non_triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], offset=0, order="asc", passed_evals=["string"], @@ -773,7 +762,6 @@ async def test_method_list_groups_with_all_params(self, async_client: AsyncCodex search_text="search_text", sort="created_at", tool_call_names=["string"], - triggered_deterministic_guardrail_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], was_cache_hit=True, ) From d7a0bd2d13602297ae20eb142b5613cae8081c3f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:31:33 +0000 Subject: [PATCH 310/320] fix(types): allow pyright to infer TypedDict types within SequenceNotStr --- src/codex/_types.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/codex/_types.py b/src/codex/_types.py index 2e4695f9..edc28a84 100644 --- a/src/codex/_types.py +++ b/src/codex/_types.py @@ -243,6 +243,9 @@ class HttpxSendArgs(TypedDict, total=False): 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: ... @@ -251,8 +254,6 @@ 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 index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... - def count(self, value: Any, /) -> int: ... def __reversed__(self) -> Iterator[_T_co]: ... else: # just point this to a normal `Sequence` at runtime to avoid having to special case From e50b9659db9d25be8cb94e3d07f2775b279d1b8b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:33:14 +0000 Subject: [PATCH 311/320] chore: add missing docstrings --- .../organization_list_members_response.py | 2 + src/codex/types/project_create_params.py | 53 ++++++++ src/codex/types/project_detect_params.py | 127 ++++++++++++++++++ src/codex/types/project_detect_response.py | 4 + src/codex/types/project_list_response.py | 55 ++++++++ src/codex/types/project_retrieve_response.py | 53 ++++++++ src/codex/types/project_return_schema.py | 53 ++++++++ src/codex/types/project_update_params.py | 53 ++++++++ src/codex/types/project_validate_params.py | 74 ++++++++++ src/codex/types/project_validate_response.py | 4 + .../types/projects/eval_create_params.py | 2 + .../types/projects/eval_list_response.py | 4 + .../types/projects/eval_update_params.py | 4 + .../query_log_list_by_group_response.py | 12 ++ .../query_log_list_groups_response.py | 10 ++ .../types/projects/query_log_list_response.py | 10 ++ .../projects/query_log_retrieve_response.py | 10 ++ ...remediation_list_resolved_logs_response.py | 10 ++ 18 files changed, 540 insertions(+) diff --git a/src/codex/types/organization_list_members_response.py b/src/codex/types/organization_list_members_response.py index 1fa593ea..f37c1f19 100644 --- a/src/codex/types/organization_list_members_response.py +++ b/src/codex/types/organization_list_members_response.py @@ -9,6 +9,8 @@ class OrganizationListMembersResponseItem(BaseModel): + """Schema for public organization member information.""" + email: str name: str diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py index 4704f638..bd84b1c0 100644 --- a/src/codex/types/project_create_params.py +++ b/src/codex/types/project_create_params.py @@ -39,6 +39,8 @@ class ProjectCreateParams(TypedDict, total=False): class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -56,6 +58,11 @@ class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): + """A custom evaluation metric created by users. + + The TLMEvalSchema are mutable and stored in the database. + """ + criteria: Required[str] """ The evaluation criteria text that describes what aspect is being evaluated and @@ -120,10 +127,14 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): class ConfigEvalConfigCustomEvals(TypedDict, total=False): + """Configuration for custom evaluation metrics.""" + evals: Dict[str, ConfigEvalConfigCustomEvalsEvals] class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -141,6 +152,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDic class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -179,6 +196,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -196,6 +215,12 @@ class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total= class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -234,6 +259,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -251,6 +278,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedD class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -289,6 +322,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -306,6 +341,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDi class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -344,6 +385,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -361,6 +404,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -399,6 +448,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): class ConfigEvalConfigDefaultEvals(TypedDict, total=False): + """Configuration for default evaluation metrics.""" + context_sufficiency: ConfigEvalConfigDefaultEvalsContextSufficiency """A pre-configured evaluation metric from TrustworthyRAG or built into the system. @@ -436,6 +487,8 @@ class ConfigEvalConfigDefaultEvals(TypedDict, total=False): class ConfigEvalConfig(TypedDict, total=False): + """Configuration for project-specific evaluation metrics""" + custom_evals: ConfigEvalConfigCustomEvals """Configuration for custom evaluation metrics.""" diff --git a/src/codex/types/project_detect_params.py b/src/codex/types/project_detect_params.py index 8e93971b..9cbf9bcd 100644 --- a/src/codex/types/project_detect_params.py +++ b/src/codex/types/project_detect_params.py @@ -440,6 +440,8 @@ class ResponseChatCompletionTyped(TypedDict, total=False): class EvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -457,6 +459,11 @@ class EvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False): class EvalConfigCustomEvalsEvals(TypedDict, total=False): + """A custom evaluation metric created by users. + + The TLMEvalSchema are mutable and stored in the database. + """ + criteria: Required[str] """ The evaluation criteria text that describes what aspect is being evaluated and @@ -521,10 +528,14 @@ class EvalConfigCustomEvalsEvals(TypedDict, total=False): class EvalConfigCustomEvals(TypedDict, total=False): + """Configuration for custom evaluation metrics.""" + evals: Dict[str, EvalConfigCustomEvalsEvals] class EvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -542,6 +553,12 @@ class EvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDict, tot class EvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -580,6 +597,8 @@ class EvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): class EvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -597,6 +616,12 @@ class EvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total=False) class EvalConfigDefaultEvalsQueryEase(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -635,6 +660,8 @@ class EvalConfigDefaultEvalsQueryEase(TypedDict, total=False): class EvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -652,6 +679,12 @@ class EvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedDict, t class EvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -690,6 +723,8 @@ class EvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): class EvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -707,6 +742,12 @@ class EvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDict, to class EvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -745,6 +786,8 @@ class EvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): class EvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -762,6 +805,12 @@ class EvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, total= class EvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -800,6 +849,8 @@ class EvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): class EvalConfigDefaultEvals(TypedDict, total=False): + """Configuration for default evaluation metrics.""" + context_sufficiency: EvalConfigDefaultEvalsContextSufficiency """A pre-configured evaluation metric from TrustworthyRAG or built into the system. @@ -837,6 +888,8 @@ class EvalConfigDefaultEvals(TypedDict, total=False): class EvalConfig(TypedDict, total=False): + """All of the evals that should be used for this query""" + custom_evals: EvalConfigCustomEvals """Configuration for custom evaluation metrics.""" @@ -1041,6 +1094,80 @@ class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): class Options(TypedDict, total=False): + """ + Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the quality preset. + + For all options described below, higher settings will lead to longer runtimes and may consume more tokens internally. + You may not be able to run long prompts (or prompts with long responses) in your account, + unless your token/rate limits are increased. If you hit token limit issues, try lower/less expensive TLMOptions + to be able to run longer prompts/responses, or contact Cleanlab to increase your limits. + + The default values corresponding to each quality preset are: + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, `reasoning_effort` = `"none"`. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. + You can set custom values for these arguments regardless of the quality preset specified. + + Args: + model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). + - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". + - Recommended models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". + - Recommended models for low latency/costs: "gpt-4.1-nano", "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + This parameter has no effect when `disable_trustworthiness` is True. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include - "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. + + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. + """ + custom_eval_criteria: Iterable[object] disable_persistence: bool diff --git a/src/codex/types/project_detect_response.py b/src/codex/types/project_detect_response.py index df03c864..ff0d6ceb 100644 --- a/src/codex/types/project_detect_response.py +++ b/src/codex/types/project_detect_response.py @@ -40,6 +40,10 @@ class EvalScores(BaseModel): class GuardrailedFallback(BaseModel): + """ + Name, fallback message, fallback priority, and fallback type of the triggered guardrail with the highest fallback priority + """ + message: str """ Fallback message to use if this eval fails and causes the response to be diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py index e4ce5585..d528e470 100644 --- a/src/codex/types/project_list_response.py +++ b/src/codex/types/project_list_response.py @@ -30,6 +30,8 @@ class ProjectConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -47,6 +49,11 @@ class ProjectConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): + """A custom evaluation metric created by users. + + The TLMEvalSchema are mutable and stored in the database. + """ + criteria: str """ The evaluation criteria text that describes what aspect is being evaluated and @@ -117,10 +124,14 @@ class ProjectConfigEvalConfigCustomEvalsEvals(BaseModel): class ProjectConfigEvalConfigCustomEvals(BaseModel): + """Configuration for custom evaluation metrics.""" + evals: Optional[Dict[str, ProjectConfigEvalConfigCustomEvalsEvals]] = None class ProjectConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -138,6 +149,12 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(B class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -182,6 +199,8 @@ class ProjectConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): class ProjectConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -199,6 +218,12 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel) class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -243,6 +268,8 @@ class ProjectConfigEvalConfigDefaultEvalsQueryEase(BaseModel): class ProjectConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -260,6 +287,12 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -304,6 +337,8 @@ class ProjectConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -321,6 +356,12 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback( class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -365,6 +406,8 @@ class ProjectConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): class ProjectConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -382,6 +425,12 @@ class ProjectConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(Base class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -426,6 +475,8 @@ class ProjectConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): class ProjectConfigEvalConfigDefaultEvals(BaseModel): + """Configuration for default evaluation metrics.""" + context_sufficiency: Optional[ProjectConfigEvalConfigDefaultEvalsContextSufficiency] = None """A pre-configured evaluation metric from TrustworthyRAG or built into the system. @@ -463,6 +514,8 @@ class ProjectConfigEvalConfigDefaultEvals(BaseModel): class ProjectConfigEvalConfig(BaseModel): + """Configuration for project-specific evaluation metrics""" + custom_evals: Optional[ProjectConfigEvalConfigCustomEvals] = None """Configuration for custom evaluation metrics.""" @@ -522,6 +575,8 @@ class Project(BaseModel): class Filters(BaseModel): + """Applied filters for the projects list request""" + query: Optional[str] = None diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py index 8fe77415..abc05ad5 100644 --- a/src/codex/types/project_retrieve_response.py +++ b/src/codex/types/project_retrieve_response.py @@ -28,6 +28,8 @@ class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -45,6 +47,11 @@ class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): class ConfigEvalConfigCustomEvalsEvals(BaseModel): + """A custom evaluation metric created by users. + + The TLMEvalSchema are mutable and stored in the database. + """ + criteria: str """ The evaluation criteria text that describes what aspect is being evaluated and @@ -115,10 +122,14 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): class ConfigEvalConfigCustomEvals(BaseModel): + """Configuration for custom evaluation metrics.""" + evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -136,6 +147,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseMode class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -180,6 +197,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -197,6 +216,12 @@ class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -241,6 +266,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -258,6 +285,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseMo class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -302,6 +335,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -319,6 +354,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseMod class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -363,6 +404,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -380,6 +423,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -424,6 +473,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): class ConfigEvalConfigDefaultEvals(BaseModel): + """Configuration for default evaluation metrics.""" + context_sufficiency: Optional[ConfigEvalConfigDefaultEvalsContextSufficiency] = None """A pre-configured evaluation metric from TrustworthyRAG or built into the system. @@ -461,6 +512,8 @@ class ConfigEvalConfigDefaultEvals(BaseModel): class ConfigEvalConfig(BaseModel): + """Configuration for project-specific evaluation metrics""" + custom_evals: Optional[ConfigEvalConfigCustomEvals] = None """Configuration for custom evaluation metrics.""" diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py index 423d0ce2..07a3a9ba 100644 --- a/src/codex/types/project_return_schema.py +++ b/src/codex/types/project_return_schema.py @@ -28,6 +28,8 @@ class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -45,6 +47,11 @@ class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(BaseModel): class ConfigEvalConfigCustomEvalsEvals(BaseModel): + """A custom evaluation metric created by users. + + The TLMEvalSchema are mutable and stored in the database. + """ + criteria: str """ The evaluation criteria text that describes what aspect is being evaluated and @@ -115,10 +122,14 @@ class ConfigEvalConfigCustomEvalsEvals(BaseModel): class ConfigEvalConfigCustomEvals(BaseModel): + """Configuration for custom evaluation metrics.""" + evals: Optional[Dict[str, ConfigEvalConfigCustomEvalsEvals]] = None class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -136,6 +147,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(BaseMode class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -180,6 +197,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(BaseModel): class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -197,6 +216,12 @@ class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(BaseModel): class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -241,6 +266,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(BaseModel): class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -258,6 +285,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(BaseMo class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -302,6 +335,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(BaseModel): class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -319,6 +354,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(BaseMod class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -363,6 +404,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(BaseModel): class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -380,6 +423,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(BaseModel): class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + display_name: str """Human-friendly name for display. @@ -424,6 +473,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(BaseModel): class ConfigEvalConfigDefaultEvals(BaseModel): + """Configuration for default evaluation metrics.""" + context_sufficiency: Optional[ConfigEvalConfigDefaultEvalsContextSufficiency] = None """A pre-configured evaluation metric from TrustworthyRAG or built into the system. @@ -461,6 +512,8 @@ class ConfigEvalConfigDefaultEvals(BaseModel): class ConfigEvalConfig(BaseModel): + """Configuration for project-specific evaluation metrics""" + custom_evals: Optional[ConfigEvalConfigCustomEvals] = None """Configuration for custom evaluation metrics.""" diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py index 3557c2d0..68cb0d34 100644 --- a/src/codex/types/project_update_params.py +++ b/src/codex/types/project_update_params.py @@ -37,6 +37,8 @@ class ProjectUpdateParams(TypedDict, total=False): class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -54,6 +56,11 @@ class ConfigEvalConfigCustomEvalsEvalsGuardrailedFallback(TypedDict, total=False class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): + """A custom evaluation metric created by users. + + The TLMEvalSchema are mutable and stored in the database. + """ + criteria: Required[str] """ The evaluation criteria text that describes what aspect is being evaluated and @@ -118,10 +125,14 @@ class ConfigEvalConfigCustomEvalsEvals(TypedDict, total=False): class ConfigEvalConfigCustomEvals(TypedDict, total=False): + """Configuration for custom evaluation metrics.""" + evals: Dict[str, ConfigEvalConfigCustomEvalsEvals] class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -139,6 +150,12 @@ class ConfigEvalConfigDefaultEvalsContextSufficiencyGuardrailedFallback(TypedDic class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -177,6 +194,8 @@ class ConfigEvalConfigDefaultEvalsContextSufficiency(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -194,6 +213,12 @@ class ConfigEvalConfigDefaultEvalsQueryEaseGuardrailedFallback(TypedDict, total= class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -232,6 +257,8 @@ class ConfigEvalConfigDefaultEvalsQueryEase(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -249,6 +276,12 @@ class ConfigEvalConfigDefaultEvalsResponseGroundednessGuardrailedFallback(TypedD class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -287,6 +320,8 @@ class ConfigEvalConfigDefaultEvalsResponseGroundedness(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -304,6 +339,12 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulnessGuardrailedFallback(TypedDi class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -342,6 +383,8 @@ class ConfigEvalConfigDefaultEvalsResponseHelpfulness(TypedDict, total=False): class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -359,6 +402,12 @@ class ConfigEvalConfigDefaultEvalsTrustworthinessGuardrailedFallback(TypedDict, class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): + """A pre-configured evaluation metric from TrustworthyRAG or built into the system. + + The evaluation criteria and identifiers are immutable and system-managed, + while other properties like thresholds and priorities can be configured. + """ + eval_key: Required[str] """ Unique key for eval metric - currently maps to the TrustworthyRAG name property @@ -397,6 +446,8 @@ class ConfigEvalConfigDefaultEvalsTrustworthiness(TypedDict, total=False): class ConfigEvalConfigDefaultEvals(TypedDict, total=False): + """Configuration for default evaluation metrics.""" + context_sufficiency: ConfigEvalConfigDefaultEvalsContextSufficiency """A pre-configured evaluation metric from TrustworthyRAG or built into the system. @@ -434,6 +485,8 @@ class ConfigEvalConfigDefaultEvals(TypedDict, total=False): class ConfigEvalConfig(TypedDict, total=False): + """Configuration for project-specific evaluation metrics""" + custom_evals: ConfigEvalConfigCustomEvals """Configuration for custom evaluation metrics.""" diff --git a/src/codex/types/project_validate_params.py b/src/codex/types/project_validate_params.py index 0efa4309..1ea53923 100644 --- a/src/codex/types/project_validate_params.py +++ b/src/codex/types/project_validate_params.py @@ -644,6 +644,80 @@ class MessageChatCompletionDeveloperMessageParam(TypedDict, total=False): class Options(TypedDict, total=False): + """ + Typed dict of advanced configuration options for the Trustworthy Language Model. + Many of these configurations are determined by the quality preset selected + (learn about quality presets in the TLM [initialization method](./#class-tlm)). + Specifying TLMOptions values directly overrides any default values set from the quality preset. + + For all options described below, higher settings will lead to longer runtimes and may consume more tokens internally. + You may not be able to run long prompts (or prompts with long responses) in your account, + unless your token/rate limits are increased. If you hit token limit issues, try lower/less expensive TLMOptions + to be able to run longer prompts/responses, or contact Cleanlab to increase your limits. + + The default values corresponding to each quality preset are: + - **best:** `num_consistency_samples` = 8, `num_self_reflections` = 3, `reasoning_effort` = `"high"`. + - **high:** `num_consistency_samples` = 4, `num_self_reflections` = 3, `reasoning_effort` = `"high"`. + - **medium:** `num_consistency_samples` = 0, `num_self_reflections` = 3, `reasoning_effort` = `"high"`. + - **low:** `num_consistency_samples` = 0, `num_self_reflections` = 3, `reasoning_effort` = `"none"`. + - **base:** `num_consistency_samples` = 0, `num_self_reflections` = 1, `reasoning_effort` = `"none"`. + + By default, TLM uses the: "medium" `quality_preset`, "gpt-4.1-mini" base `model`, and `max_tokens` is set to 512. + You can set custom values for these arguments regardless of the quality preset specified. + + Args: + model ({"gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "gpt-4.5-preview", "gpt-4o-mini", "gpt-4o", "o3-mini", "o1", "o1-mini", "gpt-4", "gpt-3.5-turbo-16k", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-sonnet-v2", "claude-3.5-sonnet", "claude-3.5-haiku", "claude-3-haiku", "nova-micro", "nova-lite", "nova-pro"}, default = "gpt-4.1-mini"): Underlying base LLM to use (better models yield better results, faster models yield faster results). + - Models still in beta: "o3", "o1", "o4-mini", "o3-mini", "o1-mini", "gpt-4.5-preview", "claude-opus-4-0", "claude-sonnet-4-0", "claude-3.7-sonnet", "claude-3.5-haiku". + - Recommended models for accuracy: "gpt-5", "gpt-4.1", "o4-mini", "o3", "claude-opus-4-0", "claude-sonnet-4-0". + - Recommended models for low latency/costs: "gpt-4.1-nano", "nova-micro". + + log (list[str], default = []): optionally specify additional logs or metadata that TLM should return. + For instance, include "explanation" here to get explanations of why a response is scored with low trustworthiness. + + custom_eval_criteria (list[dict[str, Any]], default = []): optionally specify custom evalution criteria beyond the built-in trustworthiness scoring. + The expected input format is a list of dictionaries, where each dictionary has the following keys: + - name: Name of the evaluation criteria. + - criteria: Instructions specifying the evaluation criteria. + + max_tokens (int, default = 512): the maximum number of tokens that can be generated in the response from `TLM.prompt()` as well as during internal trustworthiness scoring. + If you experience token/rate-limit errors, try lowering this number. + For OpenAI models, this parameter must be between 64 and 4096. For Claude models, this parameter must be between 64 and 512. + + reasoning_effort ({"none", "low", "medium", "high"}, default = "high"): how much internal LLM calls are allowed to reason (number of thinking tokens) + when generating alternative possible responses and reflecting on responses during trustworthiness scoring. + Reduce this value to reduce runtimes. Higher values may improve trust scoring. + + num_self_reflections (int, default = 3): the number of different evaluations to perform where the LLM reflects on the response, a factor affecting trust scoring. + The maximum number currently supported is 3. Lower values can reduce runtimes. + Reflection helps quantify aleatoric uncertainty associated with challenging prompts and catches responses that are noticeably incorrect/bad upon further analysis. + This parameter has no effect when `disable_trustworthiness` is True. + + num_consistency_samples (int, default = 8): the amount of internal sampling to measure LLM response consistency, a factor affecting trust scoring. + Must be between 0 and 20. Lower values can reduce runtimes. + Measuring consistency helps quantify the epistemic uncertainty associated with + strange prompts or prompts that are too vague/open-ended to receive a clearly defined 'good' response. + TLM measures consistency via the degree of contradiction between sampled responses that the model considers plausible. + This parameter has no effect when `disable_trustworthiness` is True. + + similarity_measure ({"semantic", "string", "embedding", "embedding_large", "code", "discrepancy"}, default = "discrepancy"): how the + trustworthiness scoring's consistency algorithm measures similarity between alternative responses considered plausible by the model. + Supported similarity measures include - "semantic" (based on natural language inference), + "embedding" (based on vector embedding similarity), "embedding_large" (based on a larger embedding model), + "code" (based on model-based analysis designed to compare code), "discrepancy" (based on model-based analysis of possible discrepancies), + and "string" (based on character/word overlap). Set this to "string" for minimal runtimes. + This parameter has no effect when `num_consistency_samples = 0`. + + num_candidate_responses (int, default = 1): how many alternative candidate responses are internally generated in `TLM.prompt()`. + `TLM.prompt()` scores the trustworthiness of each candidate response, and then returns the most trustworthy one. + You can auto-improve responses by increasing this parameter, but at higher runtimes/costs. + This parameter must be between 1 and 20. It has no effect on `TLM.score()`. + When this parameter is 1, `TLM.prompt()` simply returns a standard LLM response and does not attempt to auto-improve it. + This parameter has no effect when `disable_trustworthiness` is True. + + disable_trustworthiness (bool, default = False): if True, TLM will not compute trust scores, + useful if you only want to compute custom evaluation criteria. + """ + custom_eval_criteria: Iterable[object] disable_persistence: bool diff --git a/src/codex/types/project_validate_response.py b/src/codex/types/project_validate_response.py index b9166c20..895db6fb 100644 --- a/src/codex/types/project_validate_response.py +++ b/src/codex/types/project_validate_response.py @@ -74,6 +74,10 @@ class EvalScores(BaseModel): class GuardrailedFallback(BaseModel): + """ + Name, fallback message, fallback priority, and fallback type of the triggered guardrail with the highest fallback priority + """ + message: str """ Fallback message to use if this eval fails and causes the response to be diff --git a/src/codex/types/projects/eval_create_params.py b/src/codex/types/projects/eval_create_params.py index d4ec41e6..d319f92b 100644 --- a/src/codex/types/projects/eval_create_params.py +++ b/src/codex/types/projects/eval_create_params.py @@ -73,6 +73,8 @@ class EvalCreateParams(TypedDict, total=False): class GuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be diff --git a/src/codex/types/projects/eval_list_response.py b/src/codex/types/projects/eval_list_response.py index 2aa0d755..47bdd3de 100644 --- a/src/codex/types/projects/eval_list_response.py +++ b/src/codex/types/projects/eval_list_response.py @@ -9,6 +9,8 @@ class EvalGuardrailedFallback(BaseModel): + """message, priority, type""" + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -96,6 +98,8 @@ class Eval(BaseModel): class EvalListResponse(BaseModel): + """Schema for paginated evals response.""" + evals: List[Eval] total_count: int diff --git a/src/codex/types/projects/eval_update_params.py b/src/codex/types/projects/eval_update_params.py index 7da4e1ee..87dc9408 100644 --- a/src/codex/types/projects/eval_update_params.py +++ b/src/codex/types/projects/eval_update_params.py @@ -83,6 +83,8 @@ class CustomEvalCreateOrUpdateSchema(TypedDict, total=False): class CustomEvalCreateOrUpdateSchemaGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be @@ -137,6 +139,8 @@ class DefaultEvalUpdateSchema(TypedDict, total=False): class DefaultEvalUpdateSchemaGuardrailedFallback(TypedDict, total=False): + """message, priority, type""" + message: Required[str] """ Fallback message to use if this eval fails and causes the response to be diff --git a/src/codex/types/projects/query_log_list_by_group_response.py b/src/codex/types/projects/query_log_list_by_group_response.py index 34b722b4..4638a3f2 100644 --- a/src/codex/types/projects/query_log_list_by_group_response.py +++ b/src/codex/types/projects/query_log_list_by_group_response.py @@ -84,6 +84,12 @@ class QueryLogsByGroupQueryLogFormattedNonGuardrailEvalScores(BaseModel): class QueryLogsByGroupQueryLogContext(BaseModel): + """Represents a document in RAG contex. + + This schema is designed to be flexible while maintaining structure for RAG systems. + It supports both simple string content and rich document metadata. + """ + content: str """The actual content/text of the document.""" @@ -142,6 +148,10 @@ class QueryLogsByGroupQueryLogEvaluatedResponseToolCall(BaseModel): class QueryLogsByGroupQueryLogGuardrailedFallback(BaseModel): + """ + Name, fallback message, priority, and type for for the triggered guardrail with the highest priority + """ + message: str """ Fallback message to use if this eval fails and causes the response to be @@ -546,6 +556,8 @@ class QueryLogsByGroup(BaseModel): class Filters(BaseModel): + """Applied filters for the query""" + custom_metadata_dict: Optional[object] = None created_at_end: Optional[datetime] = None diff --git a/src/codex/types/projects/query_log_list_groups_response.py b/src/codex/types/projects/query_log_list_groups_response.py index 1587af7f..c5c2a4d6 100644 --- a/src/codex/types/projects/query_log_list_groups_response.py +++ b/src/codex/types/projects/query_log_list_groups_response.py @@ -81,6 +81,12 @@ class FormattedNonGuardrailEvalScores(BaseModel): class Context(BaseModel): + """Represents a document in RAG contex. + + This schema is designed to be flexible while maintaining structure for RAG systems. + It supports both simple string content and rich document metadata. + """ + content: str """The actual content/text of the document.""" @@ -139,6 +145,10 @@ class EvaluatedResponseToolCall(BaseModel): class GuardrailedFallback(BaseModel): + """ + Name, fallback message, priority, and type for for the triggered guardrail with the highest priority + """ + message: str """ Fallback message to use if this eval fails and causes the response to be diff --git a/src/codex/types/projects/query_log_list_response.py b/src/codex/types/projects/query_log_list_response.py index e71f05b4..b558081a 100644 --- a/src/codex/types/projects/query_log_list_response.py +++ b/src/codex/types/projects/query_log_list_response.py @@ -81,6 +81,12 @@ class FormattedNonGuardrailEvalScores(BaseModel): class Context(BaseModel): + """Represents a document in RAG contex. + + This schema is designed to be flexible while maintaining structure for RAG systems. + It supports both simple string content and rich document metadata. + """ + content: str """The actual content/text of the document.""" @@ -139,6 +145,10 @@ class EvaluatedResponseToolCall(BaseModel): class GuardrailedFallback(BaseModel): + """ + Name, fallback message, priority, and type for for the triggered guardrail with the highest priority + """ + message: str """ Fallback message to use if this eval fails and causes the response to be diff --git a/src/codex/types/projects/query_log_retrieve_response.py b/src/codex/types/projects/query_log_retrieve_response.py index 61168404..5df21087 100644 --- a/src/codex/types/projects/query_log_retrieve_response.py +++ b/src/codex/types/projects/query_log_retrieve_response.py @@ -81,6 +81,12 @@ class FormattedNonGuardrailEvalScores(BaseModel): class Context(BaseModel): + """Represents a document in RAG contex. + + This schema is designed to be flexible while maintaining structure for RAG systems. + It supports both simple string content and rich document metadata. + """ + content: str """The actual content/text of the document.""" @@ -139,6 +145,10 @@ class EvaluatedResponseToolCall(BaseModel): class GuardrailedFallback(BaseModel): + """ + Name, fallback message, priority, and type for for the triggered guardrail with the highest priority + """ + message: str """ Fallback message to use if this eval fails and causes the response to be diff --git a/src/codex/types/projects/remediation_list_resolved_logs_response.py b/src/codex/types/projects/remediation_list_resolved_logs_response.py index 986f898b..d96d1299 100644 --- a/src/codex/types/projects/remediation_list_resolved_logs_response.py +++ b/src/codex/types/projects/remediation_list_resolved_logs_response.py @@ -82,6 +82,12 @@ class QueryLogFormattedNonGuardrailEvalScores(BaseModel): class QueryLogContext(BaseModel): + """Represents a document in RAG contex. + + This schema is designed to be flexible while maintaining structure for RAG systems. + It supports both simple string content and rich document metadata. + """ + content: str """The actual content/text of the document.""" @@ -140,6 +146,10 @@ class QueryLogEvaluatedResponseToolCall(BaseModel): class QueryLogGuardrailedFallback(BaseModel): + """ + Name, fallback message, priority, and type for for the triggered guardrail with the highest priority + """ + message: str """ Fallback message to use if this eval fails and causes the response to be From 3b943b1beaf30cf24a00d605533b12467ebf2029 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 03:24:10 +0000 Subject: [PATCH 312/320] chore(internal): add missing files argument to base client --- src/codex/_base_client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index e6febf3a..1ce4a390 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -1247,9 +1247,12 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1767,9 +1770,12 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return await self.request(cast_to, opts) async def put( From 47baa019cf06ee25aacfda110d519de7b0f4453b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:47:20 +0000 Subject: [PATCH 313/320] chore: speedup initial import --- src/codex/_client.py | 226 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 179 insertions(+), 47 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index 308ce9ae..1bfcb7e4 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Dict, Mapping, cast +from typing import TYPE_CHECKING, Any, Dict, Mapping, cast from typing_extensions import Self, Literal, override import httpx @@ -21,8 +21,8 @@ not_given, ) from ._utils import is_given, get_async_library +from ._compat import cached_property from ._version import __version__ -from .resources import health from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError from ._base_client import ( @@ -30,9 +30,13 @@ SyncAPIClient, AsyncAPIClient, ) -from .resources.users import users -from .resources.projects import projects -from .resources.organizations import organizations + +if TYPE_CHECKING: + from .resources import users, health, projects, organizations + from .resources.health import HealthResource, AsyncHealthResource + from .resources.users.users import UsersResource, AsyncUsersResource + from .resources.projects.projects import ProjectsResource, AsyncProjectsResource + from .resources.organizations.organizations import OrganizationsResource, AsyncOrganizationsResource __all__ = [ "ENVIRONMENTS", @@ -54,13 +58,6 @@ class Codex(SyncAPIClient): - health: health.HealthResource - organizations: organizations.OrganizationsResource - users: users.UsersResource - projects: projects.ProjectsResource - with_raw_response: CodexWithRawResponse - with_streaming_response: CodexWithStreamedResponse - # client options auth_token: str | None api_key: str | None @@ -138,12 +135,37 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.health = health.HealthResource(self) - self.organizations = organizations.OrganizationsResource(self) - self.users = users.UsersResource(self) - self.projects = projects.ProjectsResource(self) - self.with_raw_response = CodexWithRawResponse(self) - self.with_streaming_response = CodexWithStreamedResponse(self) + @cached_property + def health(self) -> HealthResource: + from .resources.health import HealthResource + + return HealthResource(self) + + @cached_property + def organizations(self) -> OrganizationsResource: + from .resources.organizations import OrganizationsResource + + return OrganizationsResource(self) + + @cached_property + def users(self) -> UsersResource: + from .resources.users import UsersResource + + return UsersResource(self) + + @cached_property + def projects(self) -> ProjectsResource: + from .resources.projects import ProjectsResource + + return ProjectsResource(self) + + @cached_property + def with_raw_response(self) -> CodexWithRawResponse: + return CodexWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CodexWithStreamedResponse: + return CodexWithStreamedResponse(self) @property @override @@ -298,13 +320,6 @@ def _make_status_error( class AsyncCodex(AsyncAPIClient): - health: health.AsyncHealthResource - organizations: organizations.AsyncOrganizationsResource - users: users.AsyncUsersResource - projects: projects.AsyncProjectsResource - with_raw_response: AsyncCodexWithRawResponse - with_streaming_response: AsyncCodexWithStreamedResponse - # client options auth_token: str | None api_key: str | None @@ -382,12 +397,37 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.health = health.AsyncHealthResource(self) - self.organizations = organizations.AsyncOrganizationsResource(self) - self.users = users.AsyncUsersResource(self) - self.projects = projects.AsyncProjectsResource(self) - self.with_raw_response = AsyncCodexWithRawResponse(self) - self.with_streaming_response = AsyncCodexWithStreamedResponse(self) + @cached_property + def health(self) -> AsyncHealthResource: + from .resources.health import AsyncHealthResource + + return AsyncHealthResource(self) + + @cached_property + def organizations(self) -> AsyncOrganizationsResource: + from .resources.organizations import AsyncOrganizationsResource + + return AsyncOrganizationsResource(self) + + @cached_property + def users(self) -> AsyncUsersResource: + from .resources.users import AsyncUsersResource + + return AsyncUsersResource(self) + + @cached_property + def projects(self) -> AsyncProjectsResource: + from .resources.projects import AsyncProjectsResource + + return AsyncProjectsResource(self) + + @cached_property + def with_raw_response(self) -> AsyncCodexWithRawResponse: + return AsyncCodexWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCodexWithStreamedResponse: + return AsyncCodexWithStreamedResponse(self) @property @override @@ -542,35 +582,127 @@ def _make_status_error( class CodexWithRawResponse: + _client: Codex + def __init__(self, client: Codex) -> None: - self.health = health.HealthResourceWithRawResponse(client.health) - self.organizations = organizations.OrganizationsResourceWithRawResponse(client.organizations) - self.users = users.UsersResourceWithRawResponse(client.users) - self.projects = projects.ProjectsResourceWithRawResponse(client.projects) + self._client = client + + @cached_property + def health(self) -> health.HealthResourceWithRawResponse: + from .resources.health import HealthResourceWithRawResponse + + return HealthResourceWithRawResponse(self._client.health) + + @cached_property + def organizations(self) -> organizations.OrganizationsResourceWithRawResponse: + from .resources.organizations import OrganizationsResourceWithRawResponse + + return OrganizationsResourceWithRawResponse(self._client.organizations) + + @cached_property + def users(self) -> users.UsersResourceWithRawResponse: + from .resources.users import UsersResourceWithRawResponse + + return UsersResourceWithRawResponse(self._client.users) + + @cached_property + def projects(self) -> projects.ProjectsResourceWithRawResponse: + from .resources.projects import ProjectsResourceWithRawResponse + + return ProjectsResourceWithRawResponse(self._client.projects) class AsyncCodexWithRawResponse: + _client: AsyncCodex + def __init__(self, client: AsyncCodex) -> None: - self.health = health.AsyncHealthResourceWithRawResponse(client.health) - self.organizations = organizations.AsyncOrganizationsResourceWithRawResponse(client.organizations) - self.users = users.AsyncUsersResourceWithRawResponse(client.users) - self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) + self._client = client + + @cached_property + def health(self) -> health.AsyncHealthResourceWithRawResponse: + from .resources.health import AsyncHealthResourceWithRawResponse + + return AsyncHealthResourceWithRawResponse(self._client.health) + + @cached_property + def organizations(self) -> organizations.AsyncOrganizationsResourceWithRawResponse: + from .resources.organizations import AsyncOrganizationsResourceWithRawResponse + + return AsyncOrganizationsResourceWithRawResponse(self._client.organizations) + + @cached_property + def users(self) -> users.AsyncUsersResourceWithRawResponse: + from .resources.users import AsyncUsersResourceWithRawResponse + + return AsyncUsersResourceWithRawResponse(self._client.users) + + @cached_property + def projects(self) -> projects.AsyncProjectsResourceWithRawResponse: + from .resources.projects import AsyncProjectsResourceWithRawResponse + + return AsyncProjectsResourceWithRawResponse(self._client.projects) class CodexWithStreamedResponse: + _client: Codex + def __init__(self, client: Codex) -> None: - self.health = health.HealthResourceWithStreamingResponse(client.health) - self.organizations = organizations.OrganizationsResourceWithStreamingResponse(client.organizations) - self.users = users.UsersResourceWithStreamingResponse(client.users) - self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) + self._client = client + + @cached_property + def health(self) -> health.HealthResourceWithStreamingResponse: + from .resources.health import HealthResourceWithStreamingResponse + + return HealthResourceWithStreamingResponse(self._client.health) + + @cached_property + def organizations(self) -> organizations.OrganizationsResourceWithStreamingResponse: + from .resources.organizations import OrganizationsResourceWithStreamingResponse + + return OrganizationsResourceWithStreamingResponse(self._client.organizations) + + @cached_property + def users(self) -> users.UsersResourceWithStreamingResponse: + from .resources.users import UsersResourceWithStreamingResponse + + return UsersResourceWithStreamingResponse(self._client.users) + + @cached_property + def projects(self) -> projects.ProjectsResourceWithStreamingResponse: + from .resources.projects import ProjectsResourceWithStreamingResponse + + return ProjectsResourceWithStreamingResponse(self._client.projects) class AsyncCodexWithStreamedResponse: + _client: AsyncCodex + def __init__(self, client: AsyncCodex) -> None: - self.health = health.AsyncHealthResourceWithStreamingResponse(client.health) - self.organizations = organizations.AsyncOrganizationsResourceWithStreamingResponse(client.organizations) - self.users = users.AsyncUsersResourceWithStreamingResponse(client.users) - self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) + self._client = client + + @cached_property + def health(self) -> health.AsyncHealthResourceWithStreamingResponse: + from .resources.health import AsyncHealthResourceWithStreamingResponse + + return AsyncHealthResourceWithStreamingResponse(self._client.health) + + @cached_property + def organizations(self) -> organizations.AsyncOrganizationsResourceWithStreamingResponse: + from .resources.organizations import AsyncOrganizationsResourceWithStreamingResponse + + return AsyncOrganizationsResourceWithStreamingResponse(self._client.organizations) + + @cached_property + def users(self) -> users.AsyncUsersResourceWithStreamingResponse: + from .resources.users import AsyncUsersResourceWithStreamingResponse + + return AsyncUsersResourceWithStreamingResponse(self._client.users) + + @cached_property + def projects(self) -> projects.AsyncProjectsResourceWithStreamingResponse: + from .resources.projects import AsyncProjectsResourceWithStreamingResponse + + return AsyncProjectsResourceWithStreamingResponse(self._client.projects) Client = Codex From 41f2cad591915ad45ec68050ee77da5ec17431f5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 04:02:57 +0000 Subject: [PATCH 314/320] fix: use async_to_httpx_files in patch method --- src/codex/_base_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 1ce4a390..b30aa775 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -1774,7 +1774,7 @@ async def patch( options: RequestOptions = {}, ) -> ResponseT: opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) From ac52655975561f0174119492c7cec64a43c23770 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 03:49:50 +0000 Subject: [PATCH 315/320] chore(internal): add `--fix` argument to lint script --- scripts/lint | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/lint b/scripts/lint index adfc6acf..5e1c2a52 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,8 +4,13 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running lints with --fix" + rye run fix:ruff +else + echo "==> Running lints" + rye run lint +fi echo "==> Making sure it imports" rye run python -c 'import codex' From a34ed6ab37dc52705a8ceaae46c04bbd0c45ab6f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 03:34:13 +0000 Subject: [PATCH 316/320] chore(internal): codegen related update --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 56f06ba4..99a76659 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2025 Codex +Copyright 2026 Codex Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 50ea75900b33cdcd8e8f7411b86987de6bc6be00 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 03:35:53 +0000 Subject: [PATCH 317/320] fix(client): loosen auth header validation --- src/codex/_client.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index 1bfcb7e4..2661ae24 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -209,19 +209,13 @@ def default_headers(self) -> dict[str, str | Omit]: @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.auth_token and headers.get("Authorization"): - return - if isinstance(custom_headers.get("Authorization"), Omit): + if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): return - if self.api_key and headers.get("X-API-Key"): - return - if isinstance(custom_headers.get("X-API-Key"), Omit): + if headers.get("X-API-Key") or isinstance(custom_headers.get("X-API-Key"), Omit): return - if self.access_key and headers.get("X-Access-Key"): - return - if isinstance(custom_headers.get("X-Access-Key"), Omit): + if headers.get("X-Access-Key") or isinstance(custom_headers.get("X-Access-Key"), Omit): return raise TypeError( @@ -471,19 +465,13 @@ def default_headers(self) -> dict[str, str | Omit]: @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.auth_token and headers.get("Authorization"): - return - if isinstance(custom_headers.get("Authorization"), Omit): + if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): return - if self.api_key and headers.get("X-API-Key"): - return - if isinstance(custom_headers.get("X-API-Key"), Omit): + if headers.get("X-API-Key") or isinstance(custom_headers.get("X-API-Key"), Omit): return - if self.access_key and headers.get("X-Access-Key"): - return - if isinstance(custom_headers.get("X-Access-Key"), Omit): + if headers.get("X-Access-Key") or isinstance(custom_headers.get("X-Access-Key"), Omit): return raise TypeError( From c153b50d0fbd62a7191a25b1636cc48f8d7c7372 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 04:41:35 +0000 Subject: [PATCH 318/320] chore(internal): codegen related update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42ac4876..a6cf5c22 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The REST API documentation can be found on [help.cleanlab.ai](https://help.clean ```sh # install from PyPI -pip install --pre codex-sdk +pip install '--pre codex-sdk' ``` ## Usage @@ -78,7 +78,7 @@ You can enable this by installing `aiohttp`: ```sh # install from PyPI -pip install --pre codex-sdk[aiohttp] +pip install '--pre codex-sdk[aiohttp]' ``` Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: From f9ef5a6c771288892d65c5b49e45b3b6e67e0f24 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 04:47:10 +0000 Subject: [PATCH 319/320] feat(client): add support for binary request streaming --- src/codex/_base_client.py | 145 ++++++++++++++++++++++++++--- src/codex/_models.py | 17 +++- src/codex/_types.py | 9 ++ tests/test_client.py | 187 +++++++++++++++++++++++++++++++++++++- 4 files changed, 344 insertions(+), 14 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index b30aa775..c4b6303f 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -9,6 +9,7 @@ import inspect import logging import platform +import warnings import email.utils from types import TracebackType from random import random @@ -51,9 +52,11 @@ ResponseT, AnyMapping, PostParser, + BinaryTypes, RequestFiles, HttpxSendArgs, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, not_given, @@ -477,8 +480,19 @@ def _build_request( retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): - log.debug("Request options: %s", model_dump(options, exclude_unset=True)) - + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) kwargs: dict[str, Any] = {} json_data = options.json_data @@ -532,7 +546,13 @@ def _build_request( is_body_allowed = options.method.lower() != "get" if is_body_allowed: - if isinstance(json_data, bytes): + 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 else: kwargs["json"] = json_data if is_given(json_data) else None @@ -1194,6 +1214,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, @@ -1206,6 +1227,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1219,6 +1241,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1231,13 +1254,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)) @@ -1247,11 +1282,23 @@ def patch( *, 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="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1261,11 +1308,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) @@ -1275,9 +1334,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( @@ -1717,6 +1786,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, @@ -1729,6 +1799,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], @@ -1742,6 +1813,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1754,13 +1826,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) @@ -1770,11 +1854,28 @@ async def patch( *, 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="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, ) return await self.request(cast_to, opts) @@ -1784,11 +1885,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) @@ -1798,9 +1911,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( diff --git a/src/codex/_models.py b/src/codex/_models.py index ca9500b2..29070e05 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -3,7 +3,20 @@ import os import inspect import weakref -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast +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, @@ -787,6 +800,7 @@ 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 @@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel): 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 diff --git a/src/codex/_types.py b/src/codex/_types.py index edc28a84..151b4e96 100644 --- a/src/codex/_types.py +++ b/src/codex/_types.py @@ -13,9 +13,11 @@ Mapping, TypeVar, Callable, + Iterable, Iterator, Optional, Sequence, + AsyncIterable, ) from typing_extensions import ( Set, @@ -56,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, diff --git a/tests/test_client.py b/tests/test_client.py index 45622f7d..9a2dc2fb 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -8,10 +8,11 @@ import json import asyncio import inspect +import dataclasses import tracemalloc -from typing import Any, Union, cast +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock -from typing_extensions import Literal +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -36,6 +37,7 @@ from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") auth_token = "My Auth Token" @@ -50,6 +52,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: Codex | AsyncCodex) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -495,6 +548,70 @@ def test_multipart_repeating_array(self, client: Codex) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload(self, respx_mock: MockRouter, client: Codex) -> 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 Codex( + base_url=base_url, + auth_token=auth_token, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Codex) -> 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: Codex) -> None: class Model1(BaseModel): @@ -1343,6 +1460,72 @@ def test_multipart_repeating_array(self, async_client: AsyncCodex) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncCodex) -> 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 AsyncCodex( + base_url=base_url, + auth_token=auth_token, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncCodex + ) -> 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: AsyncCodex) -> None: class Model1(BaseModel): From b9d7e346fd8f5e9a085be6cc8d14080706104432 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 04:10:30 +0000 Subject: [PATCH 320/320] chore(internal): update `actions/checkout` version --- .github/workflows/ci.yml | 4 ++-- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49f178b1..ce12cdbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/codex-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: | @@ -44,7 +44,7 @@ jobs: id-token: write runs-on: ${{ github.repository == 'stainless-sdks/codex-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 7c078aa3..7381f407 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 2ce3cdc2..3c94160c 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'cleanlab/codex-python' && (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: |